Migrating a Quart project with websockets from asyncio to trio - python

I'm trying to convert my asyncio project to trio.
I understand that I have to use memory channels instead of Queues but for some reason I don't have the result I'm expecting.
My main problem is that when I run two clients, the first one does not get notified if the second one leaves (broadcasting the 'part' message from the server raises an error).
Another problem is that sometimes the client exits immediately when opening the websocket.
When I use asyncio, everything works fine.
Here is the stack trace I get when the second client is disconnecting:
[2021-07-30 18:39:51,899] ERROR in app: Exception on websocket /ws
Traceback (most recent call last):
File "/tmp/debug/venv/lib/python3.9/site-packages/quart_trio/app.py", line 175, in handle_websocket
return await self.full_dispatch_websocket(websocket_context)
File "/tmp/debug/venv/lib/python3.9/site-packages/quart_trio/app.py", line 197, in full_dispatch_websocket
result = await self.handle_user_exception(error)
File "/tmp/debug/venv/lib/python3.9/site-packages/quart_trio/app.py", line 166, in handle_user_exception
raise error
File "/tmp/debug/venv/lib/python3.9/site-packages/quart_trio/app.py", line 195, in full_dispatch_websocket
result = await self.dispatch_websocket(websocket_context)
File "/tmp/debug/venv/lib/python3.9/site-packages/quart/app.py", line 1651, in dispatch_websocket
return await self.ensure_async(handler)(**websocket_.view_args)
File "/tmp/debug/server.py", line 103, in wsocket
nursery.start_soon(receiving, u)
File "/tmp/debug/venv/lib/python3.9/site-packages/trio/_core/_run.py", line 815, in __aexit__
raise combined_error_from_nursery
trio.MultiError: Cancelled(), Cancelled(), Cancelled()
Details of embedded exception 1:
Traceback (most recent call last):
File "/tmp/debug/venv/lib/python3.9/site-packages/trio/_core/_run.py", line 1172, in raise_cancel
raise Cancelled._create()
trio.Cancelled: Cancelled
Details of embedded exception 2:
Traceback (most recent call last):
File "/tmp/debug/server.py", line 68, in receiving
data = await websocket.receive_json()
File "/tmp/debug/venv/lib/python3.9/site-packages/quart/wrappers/websocket.py", line 68, in receive_json
data = await self.receive()
File "/tmp/debug/venv/lib/python3.9/site-packages/quart/wrappers/websocket.py", line 57, in receive
return await self._receive()
File "/tmp/debug/venv/lib/python3.9/site-packages/trio/_channel.py", line 314, in receive
return await trio.lowlevel.wait_task_rescheduled(abort_fn)
File "/tmp/debug/venv/lib/python3.9/site-packages/trio/_core/_traps.py", line 166, in wait_task_rescheduled
return (await _async_yield(WaitTaskRescheduled(abort_func))).unwrap()
File "/tmp/debug/venv/lib/python3.9/site-packages/outcome/_impl.py", line 138, in unwrap
raise captured_error
File "/tmp/debug/venv/lib/python3.9/site-packages/trio/_core/_run.py", line 1172, in raise_cancel
raise Cancelled._create()
trio.Cancelled: Cancelled
Details of embedded exception 3:
Traceback (most recent call last):
File "/tmp/debug/server.py", line 54, in sending
data = await u.queue_recv.receive()
File "/tmp/debug/venv/lib/python3.9/site-packages/trio/_channel.py", line 314, in receive
return await trio.lowlevel.wait_task_rescheduled(abort_fn)
File "/tmp/debug/venv/lib/python3.9/site-packages/trio/_core/_traps.py", line 166, in wait_task_rescheduled
return (await _async_yield(WaitTaskRescheduled(abort_func))).unwrap()
File "/tmp/debug/venv/lib/python3.9/site-packages/outcome/_impl.py", line 138, in unwrap
raise captured_error
File "/tmp/debug/venv/lib/python3.9/site-packages/trio/_core/_run.py", line 1172, in raise_cancel
raise Cancelled._create()
trio.Cancelled: Cancelled
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/tmp/debug/server.py", line 63, in sending
await broadcast({'type': 'part', 'data': u.name})
File "/tmp/debug/server.py", line 75, in broadcast
await user.queue_send.send(message)
File "/tmp/debug/venv/lib/python3.9/site-packages/trio/_channel.py", line 159, in send
await trio.lowlevel.checkpoint_if_cancelled()
File "/tmp/debug/venv/lib/python3.9/site-packages/trio/_core/_run.py", line 2361, in checkpoint_if_cancelled
await _core.checkpoint()
File "/tmp/debug/venv/lib/python3.9/site-packages/trio/_core/_run.py", line 2339, in checkpoint
await _core.wait_task_rescheduled(lambda _: _core.Abort.SUCCEEDED)
File "/tmp/debug/venv/lib/python3.9/site-packages/trio/_core/_traps.py", line 166, in wait_task_rescheduled
return (await _async_yield(WaitTaskRescheduled(abort_func))).unwrap()
File "/tmp/debug/venv/lib/python3.9/site-packages/outcome/_impl.py", line 138, in unwrap
raise captured_error
File "/tmp/debug/venv/lib/python3.9/site-packages/trio/_core/_run.py", line 1172, in raise_cancel
raise Cancelled._create()
trio.Cancelled: Cancelled
Here is the code (set TRIO to False to use asyncio):
server.py
#!/usr/bin/env python
from quart import Quart, websocket, request, jsonify, json
from quart_trio import QuartTrio
from functools import wraps
import uuid
import trio
import asyncio
from quart_auth import AuthUser, AuthManager, login_user, _AuthSerializer
TRIO = True
if TRIO:
app = QuartTrio(__name__)
else:
app = Quart(__name__)
app.secret_key = '**changeme**'
authorized_users = set()
class User(AuthUser):
#staticmethod
def current():
token = websocket.cookies['QUART_AUTH']
serializer = _AuthSerializer('**changeme**', 'quart auth salt')
user_id = serializer.loads(token)
for u in authorized_users:
if u.auth_id == user_id:
return u
return None
def __init__(self, auth_id):
super().__init__(auth_id)
self.name = None
self.queue = None # asyncio
self.queue_send = None #trio
self.queue_recv = None #trio
self.connected = False
self.websockets = set()
def to_dict(self):
return {
'id': self.auth_id,
'name': self.name
}
auth_manager = AuthManager()
auth_manager.user_class = User
async def sending(u: User):
await broadcast({'type': 'join', 'data': u.name})
try:
while True:
if TRIO:
data = await u.queue_recv.receive()
else:
data = await u.queue.get()
for s in u.websockets:
await s.send_json(data)
finally:
u.websockets.remove(websocket._get_current_object())
if len(u.websockets) == 0:
u.connected = False
await broadcast({'type': 'part', 'data': u.name})
async def receiving(u: User):
while True:
data = await websocket.receive_json()
if data['type'] == 'msg':
await broadcast({'type': 'msg', 'user': u.name, 'data': data['data']})
async def broadcast(message):
for user in [u for u in authorized_users if u.connected]:
if TRIO:
await user.queue_send.send(message)
else:
await user.queue.put(message)
#app.route('/api/v1/auth', methods=['POST'])
async def auth_login():
data = await request.json
user_id = str(uuid.uuid4())[:8]
u = User(user_id)
u.name = data['login'] or 'Anonymous'+user_id
if TRIO:
u.queue_send, u.queue_recv = trio.open_memory_channel(float('inf'))
else:
u.queue = asyncio.Queue()
login_user(u, True)
authorized_users.add(u)
return jsonify({'id': user_id, 'name': u.name}), 200
#app.websocket('/ws')
async def wsocket():
u = User.current()
if u is None:
return
u.websockets.add(websocket._get_current_object())
u.connected = True
if TRIO:
async with trio.open_nursery() as nursery:
nursery.start_soon(sending, u)
nursery.start_soon(receiving, u)
else:
producer = asyncio.create_task(sending(u))
consumer = asyncio.create_task(receiving(u))
await asyncio.gather(producer, consumer)
auth_manager.init_app(app)
if __name__ == "__main__":
app.run(host='localhost', port=8080)
client.py
#!/usr/bin/env python
import asks
import trio
import trio_websocket
import json
asks.init(trio)
class User:
def __init__(self, name: str="") -> None:
self.name = name
class Client(User):
def __init__(self) -> None:
super(Client, self).__init__()
self.web_url = 'http://localhost:8080/api/v1'
self.ws_url = 'ws://localhost:8080/ws'
self.ws = None
self.nursery = None
self.cookiejar = {}
async def send(self, msg: dict) -> None:
if self.ws is not None:
await self.ws.send_message(json.dumps(msg))
async def reader(self, websocket) -> None:
while True:
try:
message_raw = await websocket.get_message()
msg = json.loads(message_raw)
if msg['type'] == 'msg':
print(f"<{msg['user']}> {msg['data']}")
elif msg['type'] == 'join':
print(f"* {msg['data']} joined")
elif msg['type'] == 'part':
print(f"* {msg['data']} left")
except trio_websocket.ConnectionClosed:
break
async def login(self) -> None:
rlogin = await asks.post(self.web_url + '/auth', json={'login': self.name, 'password': 'password'})
for c in rlogin.cookies:
if c.name == 'QUART_AUTH':
self.cookiejar = {'QUART_AUTH': c.value}
async def connect(self) -> None:
await self.login()
async with trio_websocket.open_websocket_url(self.ws_url, extra_headers=[('Cookie', 'QUART_AUTH'+'='+self.cookiejar['QUART_AUTH'])]) as websocket:
self.ws = websocket
await self.send({'type': 'msg', 'data': 'hello'})
async with trio.open_nursery() as nursery:
self.nursery = nursery
nursery.start_soon(self.reader, websocket)
def run(self) -> None:
trio.run(self.connect)
c = Client()
c.name = 'clientA'
c.run()
Edit: I tested using anyio and while anyio+trio acts the same, anyio+asyncio reproduces the problem (without any exception). So I guess it comes from the Queue replacement.

Ok, #tibs, I think I've found the issue. The problem is with the way that Trio handles cancellation. For full docs, have a read of this doc:
https://trio.readthedocs.io/en/stable/reference-core.html#cancellation-and-timeouts
However, to explain what's going on here, when a user disconnects, what Quart-Trio does is raises a Cancelled exception in every coroutine that's running/waiting under that that websocket. For a websocket-user, there are two spots that will currently be waiting:
In async def sending(u: User):
async def sending(u: User):
await broadcast({'type': 'join', 'data': u.name})
try:
while True:
if TRIO:
data = await u.queue_recv.receive() <--- Code is waiting here, Cancelled is raised here
else:
data = await u.queue.get()
for s in u.websockets:
await s.send_json(data)
finally:
u.websockets.remove(websocket._get_current_object())
if len(u.websockets) == 0:
u.connected = False
await broadcast({'type': 'part', 'data': u.name})
In async def receiving(u: User):
async def receiving(u: User):
while True:
data = await websocket.receive_json() <--- Code is waiting here, Cancelled is raised here
if data['type'] == 'msg':
await broadcast({'type': 'msg', 'user': u.name, 'data': data['data']})
Okay, so what happens from here? Well, in the sending() function we move down to the finally block, which begins executing, but then we call another awaitable function:
finally:
u.websockets.remove(websocket._get_current_object())
if len(u.websockets) == 0:
u.connected = False
await broadcast({'type': 'part', 'data': u.name}) <--- we call an awaitable here
From the Trio docs:
Cancellations in Trio are “level triggered”, meaning that once a block has been cancelled, all cancellable operations in that block will keep raising Cancelled.
So when await broadcast(...) is called, it is immediately Cancelled, unlike asyncio which behaves differently. This explains why your "part" message is never sent. So when trio, if you want to do some cleanup work while you are being cancelled, you should open a new cancel scope, and shield it from being cancelled, like this:
async def sending(u: User):
await broadcast({'type': 'join', 'data': u.name})
try:
while True:
if TRIO:
data = await u.queue_recv.receive() <--- Code is waiting here, Cancelled is raised here
else:
data = await u.queue.get()
for s in u.websockets:
await s.send_json(data)
finally:
u.websockets.remove(websocket._get_current_object())
if len(u.websockets) == 0:
u.connected = False
with trio.move_on_after(5) as leaving_cancel_scope:
# Shield from the cancellation for 5s to run the broadcast of leaving
leaving_cancel_scope.shield = True
await broadcast({'type': 'part', 'data': u.name})
Or alternatively you could start the broadcast coroutine on the app nursery. Be aware that if the broadcast(...) crashes you will the crash the whole running app, unless you put a try/except in the broadcast(...) function:
async def sending(u: User):
await broadcast({'type': 'join', 'data': u.name})
try:
while True:
if TRIO:
data = await u.queue_recv.receive()
else:
data = await u.queue.get()
for s in u.websockets:
await s.send_json(data)
finally:
u.websockets.remove(websocket._get_current_object())
if len(u.websockets) == 0:
u.connected = False
app.nursery.start_soon(broadcast, {'type': 'part', 'data': u.name})
After this you still get the Cancelled exceptions flowing through to your websocket function, so you may want to catch them there. Be aware you will need to catch BaseException to catch errors, some thing like:
#app.websocket('/ws')
async def wsocket():
u = User.current()
if u is None:
return
u.websockets.add(websocket._get_current_object())
u.connected = True
if TRIO:
try:
async with trio.open_nursery() as nursery:
nursery.start_soon(sending, u)
nursery.start_soon(receiving, u)
except BaseException as e:
print(f'websocket funcs crashed with exception: {e}')
In particular this is because trio doesn't allow you to silently drop exceptions, you need to either catch them or crash. I hope this is enough to get you started on fixing the issues you are seeing.

Related

Discord bot cannot fetch channel

I am trying to code a bot that will simultaneously print new messages in a server to console and for the user to be able to send messages to the server at the same time.
import discord
from asyncio import run
from threading import Thread
intents = discord.Intents.all()
intents.members = True
client = discord.Client(intents=intents)
async def p():
ap = await client.fetch_channel(1234567890)
return ap
main_guild = run(p())
def log_msg(msge,date):
with open('log.txt','a') as f:
f.write(msge + '\n' + date)
f.close()
async def send_a_message():
while True:
await main_guild.send(input('Send message: '))
#client.event
async def on_message(message):
base_msg = str(message.author)+': '+str(message.channel.name)+': '+str(message.content)
date = str(message.created_at)
if len(message.attachments) == 0:
print(base_msg)
log_msg(base_msg,date)
return
for i in range(len(message.attachments)):
base_msg += '\n'+(message.attachments[i]).url
log_msg(base_msg,date)
t = Thread(target=lambda:run(send_a_message()))
t.start()
try:
client.run('#######################')
except discord.errors.HTTPException:
from os import system
system('kill 1')
However, I get a strange error:
Traceback (most recent call last):
File "main.py", line 13, in <module>
main_guild = run(p())
File "/nix/store/hd4cc9rh83j291r5539hkf6qd8lgiikb-python3-3.10.8/lib/python3.10/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/nix/store/hd4cc9rh83j291r5539hkf6qd8lgiikb-python3-3.10.8/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
return future.result()
File "main.py", line 10, in p
ap = await client.fetch_channel(999141783709679668)
File "/home/runner/RandomRepl/venv/lib/python3.10/site-packages/discord/client.py", line 1824, in fetch_channel
data = await self.http.get_channel(channel_id)
File "/home/runner/RandomRepl/venv/lib/python3.10/site-packages/discord/http.py", line 604, in request
if not self._global_over.is_set():
AttributeError: '_MissingSentinel' object has no attribute 'is_set'
What is causing this and how do I fix this?
Thank you.
To get the channel you are using the client. But you are trying to get the channel before actually running the client, so you can't get the channel.
You have to first run the client and then get the channel. A possibility would be to use the on_ready event (called after client.run('...')) to get the channel.
Here is an example code you can start working with:
import discord
client = discord.Client(intents=discord.Intents.all())
main_guild = None
#client.event
async def on_ready():
global main_guild
await client.wait_until_ready()
main_guild = client.get_channel(1234567890)
print('Connected')
#client.event
async def on_message(message):
if message.author.id == client.user.id: return
if message.channel.type == discord.ChannelType.private: channel = 'DM'
else: channel = message.channel.name
base_msg = f'{message.author}: {channel}: {message.content}'
await main_guild.send(base_msg)
client.run('#######################')

NATS WEBSOCKET Python WebSocket Connection

I want to do the following thing:
I want to make a websocket, which prints me all events about esports and I want to use https://sofascore.com
I've inspected the network requests as usual and it seems that I need to send a Auth WebSocket Content first, then one for subscribing the right sport and then I will receive my events I need.
I've wrote the following code:
import websockets
import asyncio
from websockets.extensions import permessage_deflate
async def esports():
async with websockets.connect('wss://ws.sofascore.com:9222/', compression='deflate') as websocket:
msg = await websocket.recv()
print(f"From Server: {msg}")
t = await websocket.send(
'CONNECT {"no_responders":true,"protocol":1,"verbose":false,"pedantic":false,"user":"none","pass":"none","lang":"nats.ws","version":"1.8.1","headers":true}')
await websocket.send("PING")
pong = await websocket.recv()
print(f"From Server: {pong}")
await websocket.send(
'SUB sport.esports 6')
while (True):
msg = await websocket.recv()
print(f"From Server: {msg}")
asyncio.get_event_loop().run_until_complete(esports())
I know that the websocket is compressed as permessage_deflate, when I saw into the request headers of the websocket.
But I still get an error:
Traceback (most recent call last):
File "C:\Users\Coding\Desktop\websockett.py", line 23, in <module>
asyncio.get_event_loop().run_until_complete(esports())
File "C:\Users\Coding\AppData\Local\Programs\Python\Python39-32\lib\asyncio\base_events.py", line 642, in run_until_complete
return future.result()
File "C:\Users\Coding\Desktop\websockett.py", line 15, in esports
await websocket.send(
File "C:\Users\Coding\AppData\Roaming\Python\Python39\site-packages\websockets\legacy\protocol.py", line 620, in send
await self.ensure_open()
File "C:\Users\Coding\AppData\Roaming\Python\Python39\site-packages\websockets\legacy\protocol.py", line 921, in ensure_open
raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedError: received 1008 (policy violation) Authentication Timeout; then sent 1008 (policy violation) Authentication Timeout
Process finished with exit code 1
EDIT:
I have now found out that the whole thing works with the Nats network. Is there any way to use Nats with a Libary that also supports the websockets?
Haven't found one on github or pypi unfortunately....
Ideally you would be able to use the nats-py library:
import asyncio
import nats
async def handler(msg):
print(f"From server: {msg}")
async def main():
nc = await nats.connect("wss://ws.sofascore.com:9222")
await nc.subscribe("sport.esports", cb=handler)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
try:
loop.run_forever()
finally:
loop.close()
However, this library does not currently support connecting with WebSockets, so
the above doesn't work (yet - it looks like it's being worked on right
now).
For your code, the only reason it fails is that the messages you're sending
don't end with \r\n, which the NATS protocol requires. The code works as
expected with this change:
import asyncio
import websockets
async def esports():
async with websockets.connect('wss://ws.sofascore.com:9222') as websocket:
msg = await websocket.recv()
print(f"From Server: {msg}")
await websocket.send(
'CONNECT {"no_responders":true,"protocol":1,"verbose":false,"pedantic":false,"user":"none","pass":"none","lang":"nats.ws","version":"1.8.1","headers":true}\r\n'
)
await websocket.send("SUB sport.esports 1\r\n")
async for msg in websocket:
print(f"From Server: {msg}")
asyncio.run(esports())
Of course this will eventually get disconnected because it doesn't respond to
PING messages. Here's a little more fleshed out script which implements enough
of the NATS protocol to log the sport.esports messages:
import asyncio
import json
import textwrap
from dataclasses import dataclass
import websockets
class SofaError(Exception):
pass
def message_string(message, data=None, pretty=False):
s = message
if data is not None:
if pretty:
s += json.dumps(data, indent=2)
else:
s += json.dumps(data, separators=(",", ":"))
return s
def log(pre, message, data=None):
print(textwrap.indent(message_string(message, data, True), pre))
def recv_log(message, data=None):
log("< ", message, data)
async def send(websocket, message, data=None):
log("> ", message, data)
data = (message_string(message, data, False) + "\r\n").encode()
await websocket.send(data)
async def connect_and_subscribe(websocket):
connect_options = {
"no_responders": True,
"protocol": 1,
"verbose": False,
"pedantic": False,
"user": "none",
"pass": "none",
"lang": "nats.ws",
"version": "1.8.1",
"headers": True,
}
await send(websocket, "CONNECT ", connect_options)
await send(websocket, "SUB sport.esports 1")
#dataclass
class NatsMsg:
subject: str
sid: str
reply_to: str
size: int
payload: bytes
def parse_msg(info_line, pending_data):
if not info_line:
raise SofaError("No payload information received")
info = [b.decode(errors="replace") for b in info_line.split(b" ")]
if len(info) == 3:
subject, sid, size = info
reply_to = None
elif len(info) == 4:
subject, sid, reply_to, size = info
else:
raise SofaError("Unrecognized info format")
try:
size = int(size)
except ValueError:
raise SofaError("Bad payload size")
if len(pending_data) < size:
raise SofaError("Incomplete payload")
payload = pending_data[:size]
pending_data = pending_data[size:]
return NatsMsg(subject, sid, reply_to, size, payload), pending_data
async def handler(websocket, ws_message, connected):
while len(ws_message):
nats_message, _, ws_message = ws_message.partition(b"\r\n")
if not nats_message:
continue
op, _, rest = nats_message.partition(b" ")
if op == b"-ERR":
recv_log(nats_message.decode(errors="replace"))
err = rest.strip(b"'").decode(errors="replace") if rest else "(No message received)"
raise SofaError(f"Server error: {err}")
elif op == b"INFO":
info_options = json.loads(rest) if rest else None
recv_log("INFO ", info_options)
if not connected:
await connect_and_subscribe(websocket)
connected = True
elif op == b"PING":
recv_log("PING")
await send(websocket, "PONG")
elif op == b"MSG":
try:
msg, ws_message = parse_msg(rest, ws_message)
except SofaError as e:
recv_log(f"MSG (Error: {e}) {rest}")
continue
msg_info = (
f"MSG subject={msg.subject} sid={msg.sid} "
f"reply-to={msg.reply_to} nbytes={msg.size}:\n"
)
try:
decoded = msg.payload.decode()
data = json.loads(decoded)
except UnicodeError:
recv_log(f"{msg_info}{msg.payload}")
except json.JSONDecodeError:
recv_log(f"{msg_info}{decoded}")
else:
recv_log(msg_info, data)
else:
recv_log(f"(Unhandled op) {nats_message.decode(errors='replace')}")
return connected
async def main():
async with websockets.connect("wss://ws.sofascore.com:9222") as websocket:
connected = False
async for message in websocket:
connected = await handler(websocket, message, connected)
if __name__ == "__main__":
asyncio.run(main())

Cannot add/append element to JSON File

So, I was making a discord bot for an RP server with 4k members in emergency.
My goal was to store the lawyers database in a json file that would be hosted on one of my computers for testing. Here is my code:
import discord
import datetime
from typing_extensions import IntVar
from discord import member
from discord.embeds import Embed
import json
from discord.ext import commands
from discord.colour import Color
import asyncio
#Vars
lawyersdict = {}
prefix = "<"
client = commands.Bot(command_prefix=prefix)
#Misc Stuff
client.remove_command("help")
#Embeds-
#RegEmbed
regembed = discord.Embed (
colour = discord.colour.Color.from_rgb(64, 255, 0),
title = 'Success',
description = 'Successfully registered you in the database as a lawyer!'
)
regembed.set_author(name='GVRP. Co',
icon_url='https://cdn.discordapp.com/avatars/921176863156609094/51441aaab15838c9a76c0488fd4ee281.webp?size=80')
regembed.timestamp = datetime.datetime.utcnow()
#Invalid
factembed = discord.Embed (
colour = discord.colour.Color.from_rgb(255, 64, 0),
title = 'Uh oh',
description = 'Seems like an error occured! Please try again.'
)
factembed.set_author(name='UselessFacts',
icon_url='https://cdn.discordapp.com/avatars/921176863156609094/51441aaab15838c9a76c0488fd4ee281.webp?size=80')
factembed.timestamp = datetime.datetime.utcnow()
#CMDS-
#Register Command
#client.command(aliases=['reg'])
async def register(ctx):
if ctx.author == client.user:
return
else:
if isinstance(ctx.channel, discord.channel.DMChannel):
await ctx.send("Sorry, you cannot use this command in a DM for security reasons.")
else:
channel = await ctx.author.create_dm()
def check(m):
return m.content is not None and m.channel == channel
await channel.send(f"Hello {ctx.author.mention}! Please tell a little bit about yourself! You have 5 minutes. (To cancel the command, type 'cancel')")
await asyncio.sleep(0.3)
descmsg = await client.wait_for('message', check=check, timeout=300)
if descmsg.content == "cancel":
await channel.send(f"Cancelled command!")
else:
await channel.send(f"Almost there! You just need to enter the hiring price! You have 2 minutes. (between 450$ & 50,000$)")
await asyncio.sleep(0.3)
pricemsg = await client.wait_for('message', check=check, timeout=180)
if any(c.isalpha() for c in pricemsg.content):
if any(c.isalpha() for c in pricemsg.content):
await channel.send("Oops, you didnt typed a number! Make sure you didnt typed it without a coma! Please re-send the command.")
else:
def PrcCheck(num: int):
if num <= 50000 and num >= 450:
return True
else:
return False
if PrcCheck(int(pricemsg.content)):
desc = {
f"{str(ctx.author.id)}" : {
"Description" : f"{descmsg.content}",
"Price" : int(pricemsg.content),
"IsActive" : "true"
}
}
jsonobj = json.dumps(desc, indent=4, sort_keys= True)
with open('lawyers.json', mode='w') as outfile:
obj = json.load(json.dump(lawyersdict, outfile))
obj.append(desc)
obj.write(jsonobj, outfile)
await channel.send(embed=regembed)
else:
await channel.send(f"The price you entered is too low or too high! Price entered: ``{pricemsg.content}``. Please re-send the command.")
#client.event
async def on_ready():
print(f"Ready! Logged in as {client.user}")
client.run("Bot Token")
Here is the Compiler Error (Python 3.9):
Ignoring exception in command register:
Traceback (most recent call last):
File "C:\Users\theli\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\ext\commands\core.py", line 85, in wrapped
ret = await coro(*args, **kwargs)
File "c:\Users\theli\OneDrive\Bureau\Discord Bots\GVRP. Co UtilBot\launcher.py", line 82, in register
obj = json.load(json.dump(lawyersdict, outfile))
File "C:\Users\theli\AppData\Local\Programs\Python\Python39\lib\json\__init__.py", line 293, in load
return loads(fp.read(),
AttributeError: 'NoneType' object has no attribute 'read'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\theli\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\ext\commands\bot.py", line 939, in invoke
await ctx.command.invoke(ctx)
File "C:\Users\theli\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\ext\commands\core.py", line 863, in invoke
await injected(*ctx.args, **ctx.kwargs)
File "C:\Users\theli\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\ext\commands\core.py", line 94, in wrapped
raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'NoneType' object has no attribute 'read'
I tried to look at other posts to see if there was a solution. But it was a post from 2015 which didn't solve my problem.
My goal here was to append details about the Discord User who entered the info and has entered my command to register.
Thank you for reading this post
obj = json.load(json.dump(lawyersdict, outfile))
You're using the result of json.dump() as the argument to json.load().
But json.dump() doesn't return anything. So you're effectively calling json.load(None).
As John Gordon said above dump return None therefore you cant load it .
To continue your comment, then yes json.load(outfile) is what you need to do but only later.
the file is open for w (write) and its currently an open file, so you can do it right away but after the with statement, by creating another io open call using the with context or with just an open.

Discord.py music bot raising TypeError when a youtube link is given in the play command

My discord bot raises TypeError when I give a youtube url in the play command.
It works when I give a music name but doesn't work when I give a url like https://youtu.be/ylVnYh-b3Qg......
I could not understand where the problem is...
Is it in my play command or the MusicSource....
Thanks in advance for your kind help.
This is the code I have written:
class MusicSource(discord.PCMVolumeTransformer):
youtube_dl.utils.bug_reports_message = lambda: '' # Suppressing Random Youtube_Dl warnings
ytdl_format_options = { # options for the youtube_dl module, for the audio quality
'format': 'bestaudio/best',
'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s',
'restrictfilenames': True,
'noplaylist': True,
'nocheckcertificate': True,
'ignoreerrors': False,
'logtostderr': False,
'quiet': True,
'no_warnings': True,
'default_search': 'ytsearch', # We will use Youtube for searching musics
'source_address': '0.0.0.0', # IPv6 causes problem sometimes so we are using IPv4
'youtube_include_dash_manifest': False # Turning off dash manifest
# (Enabling it sometimes makes the bot not play anything)
}
ytdl = youtube_dl.YoutubeDL(ytdl_format_options) # Sending the options to the Youtube_DL class
def __init__(self, source: discord.AudioSource, *, data):
self.volume = 0.4
super().__init__(source, self.volume)
self.data = data
self.title = data.get('title')
self.url = data.get('url')
self.link = data.get('webpage_url') # Fetching the youtube link for the song and
# will be used later for sending it to the user
self.time = data.get('duration')
self.img = data.get('thumbnail')
self.artist = data.get('artist')
self.likes = data.get('like_count')
self.dislikes = data.get('dislike_count')
self.albm = data.get('album')
#classmethod
async def from_url(cls, url, *, loop=None, stream=False, timestamp=0): # The player that plays the
# audio from the url
ffmpeg_options = {
'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
'options': f'-vn -ss {timestamp}'
}
loop = loop or asyncio.get_event_loop()
data = await loop.run_in_executor(None, lambda: cls.ytdl.extract_info(url, download=not stream))
if 'entries' in data:
data = data['entries'][0] # Taking the first item from the search results
filename = data['url'] if stream else cls.ytdl.prepare_filename(data)
return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data)
And the join and play command:
class Music(commands.Cog):
def __init__(self, client):
self.client = client
self.player = dict()
self.links = dict()
self.auto_loop_is_on = dict()
#commands.command(name="join", aliases=["j", "connect", "come", "fuck-on"])
async def join_voice(self, ctx: commands.Context):
voice_channel = ctx.author.voice
if voice_channel is None:
return await ctx.send("`Please connect to a voice channel first!`")
if ctx.voice_client is not None:
await ctx.voice_client.move_to(voice_channel.channel)
return await ctx.send("`Voice channel shifted!`")
elif ctx.voice_client is None:
self.player[str(ctx.guild.id)] = None
self.links[str(ctx.guild.id)] = list()
self.auto_loop_is_on[str(ctx.guild.id)] = False
await voice_channel.channel.connect()
return await ctx.send("`Voice channel connected! Use <play> command to play a music`")
#commands.command(name="play", aliases=["p"])
async def play_(self, ctx: commands.Context, timestamp: Optional[int] = 0, *, url):
if ctx.voice_client is None or ctx.author.voice is None:
return await ctx.send("`Not connected to any voice!`")
if ctx.voice_client.is_playing():
self.links[str(ctx.guild.id)].append(url)
await ctx.send(f"`Song {url} added to queue. `")
elif not ctx.voice_client.is_playing():
self.links[str(ctx.guild.id)].append(url)
await ctx.send("`Loading awesomeness! Wait a bit. `")
async with ctx.typing():
self.player[str(ctx.guild.id)] = await MusicSource.from_url(url=self.links[str(ctx.guild.id)][0],
loop=self.client.loop, stream=True,
timestamp=timestamp)
ctx.voice_client.play(self.player[str(ctx.guild.id)], after=lambda x: self.play_next(ctx))
And the Error I get:
Ignoring exception in on_command_error
Traceback (most recent call last):
File "C:\Users\Ronny\Python3.9\lib\site-packages\discord\ext\commands\core.py", line 85, in wrapped
ret = await coro(*args, **kwargs)
File "C:\Users\Ronny\PycharmProjects\XENON_DISCORD\Bot_Features\music.py", line 108, in play_
ctx.voice_client.play(self.player[str(ctx.guild.id)], after=None)
File "C:\Users\Ronny\Python3.9\lib\site-packages\discord\voice_client.py", line 561, in play
raise TypeError('source must an AudioSource not {0.__class__.__name__}'.format(source))
TypeError: source must an AudioSource not NoneType
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\Ronny\Python3.9\lib\site-packages\discord\client.py", line 343, in _run_event
await coro(*args, **kwargs)
File "C:\Users\Ronny\PycharmProjects\XENON_DISCORD\XENON.py", line 82, in on_command_error
raise err
File "C:\Users\Ronny\Python3.9\lib\site-packages\discord\ext\commands\bot.py", line 902, in invoke
await ctx.command.invoke(ctx)
File "C:\Users\Ronny\Python3.9\lib\site-packages\discord\ext\commands\core.py", line 864, in invoke
await injected(*ctx.args, **ctx.kwargs)
File "C:\Users\Ronny\Python3.9\lib\site-packages\discord\ext\commands\core.py", line 94, in wrapped
raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: TypeError: source must an AudioSource not NoneType
Try this out, it should work for both a YouTube song name and a YouTube URL:
import asyncio
import discord
import youtube_dl
from discord.ext import commands
# Suppress noise about console usage from errors
youtube_dl.utils.bug_reports_message = lambda: ''
ytdl_format_options = {
'format': 'bestaudio/best',
'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s',
'restrictfilenames': True,
'noplaylist': True,
'nocheckcertificate': True,
'ignoreerrors': False,
'logtostderr': False,
'quiet': True,
'no_warnings': True,
'default_search': 'auto',
'source_address': '0.0.0.0' # bind to ipv4 since ipv6 addresses cause issues sometimes
}
ffmpeg_options = {
'options': '-vn'
}
ytdl = youtube_dl.YoutubeDL(ytdl_format_options)
class YTDLSource(discord.PCMVolumeTransformer):
def __init__(self, source, *, data, volume=0.5):
super().__init__(source, volume)
self.data = data
self.title = data.get('title')
self.url = data.get('url')
#classmethod
async def from_url(cls, url, *, loop=None, stream=False):
loop = loop or asyncio.get_event_loop()
data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream))
if 'entries' in data:
# take first item from a playlist
data = data['entries'][0]
filename = data['url'] if stream else ytdl.prepare_filename(data)
return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data)
class Music(commands.Cog):
def __init__(self, bot):
self.bot = bot
#commands.command(description="joins a voice channel")
async def join(self, ctx):
if ctx.author.voice is None or ctx.author.voice.channel is None:
return await ctx.send('You need to be in a voice channel to use this command!')
voice_channel = ctx.author.voice.channel
if ctx.voice_client is None:
vc = await voice_channel.connect()
else:
await ctx.voice_client.move_to(voice_channel)
vc = ctx.voice_client
#commands.command(description="streams music")
async def play(self, ctx, *, url):
async with ctx.typing():
player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True)
ctx.voice_client.play(player, after=lambda e: print('Player error: %s' % e) if e else None)
await ctx.send('Now playing: {}'.format(player.title))

Python Asyncio errors: "OSError: [WinError 6] The handle is invalid" and "RuntimeError: Event loop is closed" [duplicate]

This question already has answers here:
Exception event loop is closed with aiohttp and asyncio in python 3.8
(2 answers)
Closed 2 years ago.
I am having some difficulties with properly with my code, as I get the following error after my code finishes executing while debugging on VSCode:
Exception ignored in: <function _ProactorBasePipeTransport.__del__ at 0x00000188AB3259D0>
Traceback (most recent call last):
File "c:\users\gam3p\appdata\local\programs\python\python38\lib\asyncio\proactor_events.py", line 116, in __del__
self.close()
File "c:\users\gam3p\appdata\local\programs\python\python38\lib\asyncio\proactor_events.py", line 108, in close
self._loop.call_soon(self._call_connection_lost, None)
File "c:\users\gam3p\appdata\local\programs\python\python38\lib\asyncio\base_events.py", line 719, in call_soon
self._check_closed()
File "c:\users\gam3p\appdata\local\programs\python\python38\lib\asyncio\base_events.py", line 508, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
I also get the following error if I run the code using the command line:
Cancelling an overlapped future failed
future: <_OverlappedFuture pending overlapped=<pending, 0x25822633550> cb=[_ProactorReadPipeTransport._loop_reading()]>
Traceback (most recent call last):
File "c:\users\gam3p\appdata\local\programs\python\python38\lib\asyncio\windows_events.py", line 66, in _cancel_overlapped
self._ov.cancel()
OSError: [WinError 6] The handle is invalid
The code below is an asynchronous API wrapper for Reddit. Since Reddit asks bots to be limited to a rate of 60 requests per minute, I have decided to implement a throttled loop that processes the requests in a queue, in a separate thread.
To run it, you would need to create a Reddit app and use your login credentials as well as the bot ID and secret.
If it helps, I am using Python 3.8.3 64-bit on Windows 10.
import requests
import aiohttp
import asyncio
import threading
from types import SimpleNamespace
from time import time
oauth_url = 'https://oauth.reddit.com/'
base_url = 'https://www.reddit.com/'
agent = 'windows:reddit-async:v0.1 (by /u/UrHyper)'
class Reddit:
def __init__(self, username: str, password: str, app_id: str, app_secret: str):
data = {'grant_type': 'password',
'username': username, 'password': password}
auth = requests.auth.HTTPBasicAuth(app_id, app_secret)
response = requests.post(base_url + 'api/v1/access_token',
data=data,
headers={'user-agent': agent},
auth=auth)
self.auth = response.json()
if 'error' in self.auth:
msg = f'Failed to authenticate: {self.auth["error"]}'
if 'message' in self.auth:
msg += ' - ' + self.auth['message']
raise ValueError(msg)
token = 'bearer ' + self.auth['access_token']
self.headers = {'Authorization': token, 'User-Agent': agent}
self._rate = 1
self._last_loop_time = 0.0
self._loop = asyncio.new_event_loop()
self._queue = asyncio.Queue(0)
self._end_loop = False
self._loop_thread = threading.Thread(target=self._start_loop_thread)
self._loop_thread.start()
def stop(self):
self._end_loop = True
def __del__(self):
self.stop()
def _start_loop_thread(self):
asyncio.set_event_loop(self._loop)
self._loop.run_until_complete(self._process_queue())
async def _process_queue(self):
while True:
if self._end_loop and self._queue.empty():
await self._queue.join()
break
start_time = time()
if self._last_loop_time < self._rate:
await asyncio.sleep(self._rate - self._last_loop_time)
try:
queue_item = self._queue.get_nowait()
url = queue_item['url']
callback = queue_item['callback']
data = await self._get_data(url)
self._queue.task_done()
callback(data)
except asyncio.QueueEmpty:
pass
finally:
self._last_loop_time = time() - start_time
async def _get_data(self, url):
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=self.headers) as response:
assert response.status == 200
data = await response.json()
data = SimpleNamespace(**data)
return data
async def get_bot(self, callback: callable):
url = oauth_url + 'api/v1/me'
await self._queue.put({'url': url, 'callback': callback})
async def get_user(self, user: str, callback: callable):
url = oauth_url + 'user/' + user + '/about'
await self._queue.put({'url': url, 'callback': callback})
def callback(data): print(data['name'])
async def main():
reddit = Reddit('', '', '', '')
await reddit.get_bot(lambda bot: print(bot.name))
reddit.stop()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
I ran into a similar problem with asyncio.
Since Python 3.8 they change the default event loop on Windows to ProactorEventLoop instead of SelectorEventLoop and their are some issues with it.
so adding
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
above
loop = asyncio.get_event_loop()
Will get the old eventloop back without issues.

Categories

Resources