How do I access user in Django Async view? - python-3.x

I'm trying to access user but getting an error when the view is async.
Code:
from django.http import JsonResponse
async def archive(request):
user = request.user
return JsonResponse({'msg': 'success'})
error message:
django.myproject.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
What I tried:
from django.http import JsonResponse
from asgiref.sync import sync_to_async
async def archive(request):
# user = sync_to_async(request.user)
# user = sync_to_async(request.user)()
# user = await sync_to_async(request.user)
user = await sync_to_async(request.user)()
return JsonResponse({'msg': 'success'})
Still getting the same error.
I want to access the user to check he/she has permission to archive a file.
EDIT:
I eventually figured out that I had to move it into a temporary method and run that as sync_to_async. I did this below:
def _check_user(request):
user = request.user
''' Logic here '''
return
async def archive(request):
await sync_to_async(_check_user, thread_sensitive=True)(request=request)
''' Logic here '''
And this seems to work, But not sure if this is the correct way of doing it?

Try this:
from django.http import JsonResponse
from asgiref.sync import async_to_sync, sync_to_async
#sync_to_async
def archive(request):
user = request.user
return JsonResponse({'msg': 'success'})
I don't know if it's really async, I'm trying to fix this problem too.
I've found something: https://www.valentinog.com/blog/django-q/
If the first option not work, see this link.

Related

Sync to Async Django ORM queryset foreign key property

Seemingly simple situation:
Django model has foreign key:
class Invite(models.Model):
inviter = models.ForeignKey(User, on_delete=models.CASCADE)
...
In async context, I do:
# get invite with sync_to_async decorator, then
print(invite.inviter)
Get async's favorite error:
You cannot call this from an async context - use a thread or sync_to_async
print(sync_to_async(invite.inviter)) # -> throws the same error
Sure, I can do:
#sync_to_async
def get_inviter(self, invite):
return invite.inviter
But, this is senile, if I have to do this for every queryset property call.
Is there a sane way to handle this?
Perhaps, there is a way to do this for all calls like that at once?
Yes, resolve the extra fields using select_related:
# Good: pick the foreign_key fields using select_related
user = await Invite.objects.select_related('user').aget(key=key).user
Your other string non-foreign such as strings and ints attributes should already
exist on the model.
Won't work, (although they feel like they should)
# Error django.core.exceptions.SynchronousOnlyOperation ... use sync_to_async
user = await Model.objects.aget(key=key).user
# Error (The field is actually missing from the `_state` fields cache.
user = await sync_to_async(Invite.objects.get)(key=key).user
Other examples for research
A standard aget, followed by a foreign key inspection yields a SynchronousOnlyOperation error.
I have a string key, and a ForeignKey user to the standard user model.
class Invite(models.Model):
user = fields.user_fk()
key = fields.str_uuid()
An example with alternatives that mostly don't work:
Invite = get_model('invites.Invite')
User = get_user_model()
def _get_invite(key):
return Invite.objects.get(key=key)
async def invite_get(self, key):
# (a) works, the related field is populated on response.
user = await Invite.objects.select_related('user').aget(key=key).user
async def intermediate_examples(self, key):
# works, but is clunky.
user_id = await Invite.objects.aget(key=key).user_id
# The `user_id` (any `_id` key) exists for a FK
user = await User.objects.aget(id=user_id)
async def failure_examples(self, key):
# (b) does not work.
user = await sync_to_async(Invite.objects.get)(key=key).user
invite = await sync_to_async(Invite.objects.get)(key=key)
# (c) these are not valid, although the error may say so.
user = await invite.user
user = await sync_to_async(invite.user)
# same as the example (b)
get_invite = sync_to_async(_get_invite, thread_sensitive=True)
invite = get_invite(key)
user = invite.user # Error
# (d) Does not populate the additional model
user = await Invite.objects.aget(key=key).user # Error
print(sync_to_async(invite.inviter)) # -> throws the same error
That's because it's equivalent to:
i = invite.inviter # -> throws the error here
af = sync_to_async(i)
print(af)
The correct usage is:
f = lambda: invite.inviter
af = sync_to_async(f)
i = await af()
print(i)
# As a one-liner
print(await sync_to_async(lambda: invite.inviter)())
Is there a sane way to handle this?
Perhaps, there is a way to do this for all calls like that at once?
(Disclaimer: Not tested in production.)
With nest_asyncio, you could do this:
def do(f):
import nest_asyncio
nest_asyncio.apply()
return asyncio.run(sync_to_async(f)())
print(do(lambda: invite.inviter))
Or take it even further:
class SynchronousOnlyAttributeHandler:
def __getattribute__(self, item):
from django.core.exceptions import SynchronousOnlyOperation
try:
return super().__getattribute__(item)
except SynchronousOnlyOperation:
from asgiref.sync import sync_to_async
import asyncio
import nest_asyncio
nest_asyncio.apply()
return asyncio.run(sync_to_async(lambda: self.__getattribute__(item))())
class Invite(models.Model, AsyncUnsafeAttributeHandler):
inviter = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
...
# Do this even in async context
print(invite.inviter)
Does something like this work? Instead of invite.inviter you do await async_resolve_attributes(invite, "inviter")
#sync_to_async
def async_resolve_attributes(instance, *attributes):
current_instance = instance
for attribute in attributes:
current_instance = getattr(current_instance, attribute)
resolved_attribute = current_instance
return resolved_attribute

Nextcord Ban & Kick issue

I'm trying to make a discord.py bot with the help of nextcord and i've come far enough to make the code work in theory but nothing happens when i try to kick / ban, it just throws an
nextcord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'Context' object has no attribute 'ban'
and i have no idea why it does so, i want it to work as inteded.
Note, the command(s) are class commands so the main bot loads in the command from another file.
Main Bot Script
# import nextcord
from nextcord.ext import commands
# import asyncio
import json
# Import all of the commands
from cogs.ban import ban
from cogs.hello import hello
from cogs.help import help
from cogs.info import info
from cogs.kick import kick
from cogs.test import test
#Define bot prefix and also remove the help command built in.
bot = commands.Bot(command_prefix=json.load(open("config.json"))["prefix"])
bot.remove_command("help")
#bot.event
async def on_ready():
print(f'Logged in as {bot.user}')
bot.add_cog(hello(bot))
bot.add_cog(help(bot))
bot.add_cog(info(bot))
bot.add_cog(test(bot))
bot.add_cog(ban(bot))
bot.add_cog(kick(bot))
bot.run(json.load(open("config.json"))["token"])
Problematic command
import discord
from nextcord.ext import commands
from nextcord.ext.commands import has_permissions, CheckFailure
bot = commands.bot
class ban(commands.Cog):
def __init__(self, client):
self.client = client
self._last_member = None
#commands.Cog.listener()
async def on_ready(self):
print('ban Cog Ready')
#commands.command()
#has_permissions(ban_members=True)
async def ban(ctx, user: discord.Member = None, *, Reason = None):
if user == None:
await ctx.send("Could you please enter a valid user?")
return
try:
await user.ban(reason=Reason)
await ctx.send(f'**{0}** has been banned.'.format(str(user)))
except Exception as error:
if isinstance(error, CheckFailure):
await ctx.send("Looks like you don't have the permissions to use this command.")
else:
await ctx.send(error)
You are doing:
user: discord.Member
You need to use nextcord instead.
user: nextcord.Member
#commands.command()
#has_permissions(ban_members=True)
async def ban(ctx, user: nextcord.Member = None, *, Reason = None):#
#The rest of your code here
You can do the following
For Normal bot
#client.command(name="ban", aliases=["modbancmd"], description="Bans the mentioned user.")
#commands.has_permissions(ban_members=True)
async def ban(self, ctx, member: nextcord.Member, *, reason=None):
# Code for embed (optional)
await member.ban(reason=reason)
# (optional) await ctx.send(embed=banembed)
You can do the following
For Cogs
#commands.command(name="ban", aliases=["modbancmd"], description="Bans the mentioned user.")
#commands.has_permissions(ban_members=True)
async def ban(self, ctx, member: nextcord.Member, *, reason=None):
# Code for embed (optional)
await member.ban(reason=reason)
# (optional) await ctx.send(embed=banembed)
Well, here are the problems:
Your command is in a cog therefore you need to say (self, ctx, and so on)
You need to change the discord to nextcord
You should change the commands.bot to commands.Bot()
And if I'm correct, you should change the self.client to self.bot since your commands.Bot() is defined as bot
And uhh yeah it should work perfectly after you fix those.

python3 aiogram. How to send message to user_id

from aiogram import Bot
import config
def send_message(message):
operator = Bot(config.operator_token)
operator.send_message(config.user_id, message)
def main():
send_message('hi')
I want to send message to user with id from my config.py (config.user_id). But it's not workinkg. I tried many ways for it but I always got an error.
For example like this
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7f94a9b62550>
Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x7f94a98f8040>,
23065.38)]']
connector: <aiohttp.connector.TCPConnector object at 0x7f94a9b3c400>'
If you use aiogram you must use async/await to send message.
bot = Bot(token=TOKEN)
dp = Dispatcher(bot_init)
#dp.message_handler(commands=['test'])
async def process_start_command(message: types.Message):
await bot.send_message(message.from_user.id, "test message") #like this
#await message.answer("test message")
#or like this
Maybe this?!
import asyncio
from aiogram import Bot
import config
async def send_message(message):
operator = Bot(config.operator_token)
await operator.send_message(config.user_id, message)
def main():
asyncio.run(send_message('hi'))
from aiogram import Bot, types
from aiogram.dispatcher import Dispatcher
from aiogram.utils import executor
from config import TOKEN
bot = Bot(token=TOKEN)
dp = Dispatcher(bot)
#dp.message_handler(commands=['start'])
async def process_start_command(message: types.Message):
await message.reply("Hello\nWrite me something!")
#dp.message_handler(commands=['help'])
async def process_help_command(message: types.Message):
await message.reply("Implementation EchoBot")
#dp.message_handler()
async def echo_message(msg: types.Message):
await bot.send_message(msg.from_user.id, msg.text)
if __name__ == '__main__':
executor.start_polling(dp)

instead of returning an image praw returns r/memes/hot

I want my discord.py bot to send a meme from hot posts of r/memes via PRAW. After this issue, I tried searching in the web and in the documentations, but I didn't find any method to view the image. Here is my code:
import praw
import discord
from discord.ext import commands
from discord import client
reddit = praw.Reddit(client_id="d",
client_secret="d",
user_agent="automoderatoredj by /u/taskuratik")
#boot
print("il bot si sta avviando... ")
token = "token"
client = commands.Bot(command_prefix=("/"))
#bot online
#client.event
async def on_ready():
print("il bot e' ora online")
#client.command()
async def meme(submission):
if reddit:
channel = client.get_channel(722491234991472742)
submission = reddit.subreddit("memes").hot(limit=1)
await channel.send(submission.url)
client.run(token)
Your code says:
submission = reddit.subreddit("memes").hot(limit=1)
await channel.send(submission.url)
Here, you assign a listing of one post to submission. As listing is an iterable (somewhat like a list) that contains one submission, rather than the submission itself. Unlike a list, you can't use an index to access a specific item, but there are other ways to get it. One way to get the submission is
for submission in reddit.subreddit("memes").hot(limit=1):
await channel.send(submission.url)
This allows you to change the limit and send more posts if you want.
Or, you could use next() to get the next (and only) item from the post listing:
submission = next(reddit.subreddit("memes").hot(limit=1))
await channel.send(submission.url)
This will always send just one submission, even if you change the limit parameter.
PRAW is blocking, aiohttp is not and frankly discord.py comes with aiohttp. Reddit offers an endpoint to return json data that you can prase with the json.loads() method to get raw json.
This is something I wrote to fetch images from subreddits
from aiohttp import ClientSession
from random import choice as c
from json import loads
async def get(session: object, url: object) -> object:
async with session.get(url) as response:
return await response.text()
async def reddit(sub: str):
type = ['new', 'top', 'hot', 'rising']
url = f"https://www.reddit.com/r/{sub}/{c(type)}.json?sort={c(type)}&limit=10"
async with ClientSession() as session:
data = await get(session, url)
data = loads(data)
data = data['data']['children']
url = [d['data']['url'] for d in data]
return c(url)
All you need to do is await reddit(sub= 'memes') to get the required url.

Django REST: AttributeError: 'WSGIRequest' object has no attribute 'session'

I've created a Login APIView for my Django app. I'm continually getting the error, AttributeError: 'WSGIRequest' object has no attribute 'session'. I've looked at other posts and people mention reordering the middleware placing sessionMIddleware first which doesn't work. Here is the setup of the API. The error is occurring at login(request, user).
from django.contrib.auth import authenticate, login
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializers import LoginSerializer
from rest_framework.permissions import AllowAny
class Login(APIView):
permission_classes = (AllowAny, )
authentication_classes = ()
serializer = LoginSerializer
def post(self, request, format=None):
login_form = self.serializer(data=request.data)
data = dict()
if login_form.is_valid():
username = login_form.data['username']
password = login_form.data['password']
user = authenticate(username=username, password=password)
if user is not None:
print(request.data)
login(request, user)
data['data'] = 'OK'
return Response(data=data, status=200)
else:
data['error'] = 'Invalid login information provided'
return Response(data=data, status=401)
else:
data['invalid'] = 'Bad Request, form is invalid'
return Response(data=data, status=400)
Ok, I had my middleware defined as MIDDLEWARE in my settings.py instead of MIDDLEWARE_CLASSES. Pycharm generated it for me like that when I setup the new project. I believe Django 1.10 uses MIDDLEWARE and I'm using 1.9.

Resources