Creating an async connection with psycopg3 - python

I am trying to create an async connection using psycopg3. I was using psycopg2 without async and need to move to async database functions. The docs do not give much information.
So this is what I was using with psycopg2. It worked good.
con = psycopg2.connect(host="HOSTNAME", port="PORT", database=("DATABASE", user="USER", password="PASSWORD")
cursor = con.cursor()
Then when I needed to run a query I would just use
cursor.execute(query, params)
cursor.fetchall() # or con.commit() depending on insert or select statement.
Now that I am moving to async functions, I have tried this
con = await psycopg.AsyncConnection.connect(host="HOSTNAME", port="PORT", database="DATABASE", user="USER", password="PASSWORD")
cursor = await con.cursor()
But I get the error that I cannot use await outside of a function.
The docs tell me to do this
async with await psycopg.AsyncConnection.connect() as aconn:
async with aconn.cursor() as cur:
await cur.execute(...)
So do I need to write this in every function that I want to either read or write records with?
Couple examples in my code using psycopg2 currently
async def check_guild(guild_id):
cursor.execute("SELECT guild_id, guild_name, su_id FROM guild WHERE guild_id = %s", [guild_id])
guild = cursor.fetchone()
return guild
async def config_raffle(guild_id, channel_id, channel_name, channel_cat_id, token, token_id, default_address, su_id, fee):
try:
cursor.execute("""INSERT INTO raffle_config (guild_id, channel_id, channel_name, channel_cat_id, token, default_token, default_address, su_id, fee) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (guild_id) DO UPDATE SET channel_id = EXCLUDED.channel_id, channel_name = EXCLUDED.channel_name, channel_cat_id = EXCLUDED.channel_cat_id, token = EXCLUDED.token,
default_token = EXCLUDED.default_token, default_address = EXCLUDED.default_address, su_id = EXCLUDED.su_id, fee = EXCLUDED.fee""",
(guild_id, channel_id, channel_name, channel_cat_id, token, token_id, default_address, su_id, fee))
con.commit()
except:
logging.exception("Exception", exc_info=True)
con.rollback()
print("Error: 25")
return True
So I am thinking maybe my better option is to use the AsyncConnectionPool. I have a db.py file setup like this:
import psycopg_pool
import os
import dotenv
dotenv.load_dotenv()
conninfo = f'host={os.getenv("HOSTNAME")} port={os.getenv("PORT")} dbname={os.getenv("DATABASE")} user={os.getenv("USER")} password={os.getenv("PASSWORD")}'
pool = psycopg_pool.AsyncConnectionPool(conninfo=conninfo, open=False)
async def open_pool():
await pool.open()
I open the pool when my program runs the on_ready function.
I created new tables this way just fine, but when I try to retrieve records I get this error.
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'AsyncConnection' object has no attribute 'fetchone'

Ended up sorting this out this way:
import psycopg_pool
import os
import dotenv
dotenv.load_dotenv()
conninfo = f'host={os.getenv("HOSTNAME")} port={os.getenv("PORT")} dbname={os.getenv("DATABASE")} user={os.getenv("USER")} password={os.getenv("PASSWORD")}'
pool = psycopg_pool.AsyncConnectionPool(conninfo=conninfo, open=False)
async def open_pool():
await pool.open()
await pool.wait()
print("Connection Pool Opened")
async def select_fetchall(query, args):
async with pool.connection() as conn:
async with conn.cursor() as cursor:
await cursor.execute(query, args)
results = await cursor.fetchall()
return results
async def write(query, args):
async with pool.connection() as conn:
async with conn.cursor() as cursor:
await cursor.execute(query, args)
if 'RETURNING' in query:
results = await cursor.fetchone()
return results
else:
return
Then I just call the functions when I need to read or write to the database and pass the query and args.

Related

How to add warn count to warn command with sqlite?

So im trying to make a warn command using sqlite3
everything is fine
it warns them and stores the reason, guild id and user id in the database
but i want to check for how many warns the user got using sqlite3
i tried so many things but nothing worked
any type of help will be appreciated!..
my code :-
import discord
from discord.ext import commands
import sqlite3
class warn(commands.Cog):
def __init__(self, bot):
self.bot = bot
#commands.command()
async def warn(self, ctx, member: discord.Member, reason=None):
db = sqlite3.connect("warn.db")
cursor = db.cursor()
sql = ("INSERT INTO main (user_id, reason, guild) VALUES (?, ?, ?)")
val = (member.id, reason, ctx.guild.id)
cursor.execute(sql, val)
em = discord.Embed(
description=f"<:zztick:1067013663535411280> | warned **{member.mention}** for the reason: *{reason}*",
color=0x2F3136
)
em.set_footer(text=f"warn no. {DO SOMETHING}")
await ctx.send(embed=em)
db.commit()
db.close()
async def setup(bot):
await bot.add_cog(warn(bot))
thanks for reading...
You can do something like this:
#commands.command()
async def warn(self, ctx, member: discord.Member, reason=None):
db = sqlite3.connect("warn.db")
cursor = db.cursor()
# Just for checking if user already exists in table
check_user_sql = ("SELECT COUNT(*) FROM main WHERE user_id = ? AND guild = ?")
check_user_val = (member.id, ctx.guild.id)
cursor.execute(check_user_sql, check_user_val)
user_exists = cursor.fetchone()[0]
if user_exists:
# If user exists in table, it will update their warn count
update_warn_count_sql = ("UPDATE main SET warn_count = warn_count + 1 WHERE user_id = ? AND guild = ?")
cursor.execute(update_warn_count_sql, check_user_val)
else:
# If user not exists, insert a new row with a warn count of 0
insert_user_sql = ("INSERT INTO main (user_id, reason, guild, warn_count) VALUES (?, ?, ?, 0)")
insert_user_val = (member.id, reason, ctx.guild.id)
cursor.execute(insert_user_sql, insert_user_val)
# To get current warn count of user
warn_count_sql = ("SELECT warn_count FROM main WHERE user_id = ? AND guild = ?")
cursor.execute(warn_count_sql, check_user_val)
warn_count = cursor.fetchone()[0]

How to setup on_message with sqlite3 in discord.py?

Hi I created the following event. It takes input one time after that it stores the guild id , channel id and role id in sqlite db . After that when someone in that particular guild mentions minimum 3 users in a particular channel , the bot gives them new role.
class ScrimsCog(commands.Cog, name='Scrims-Commands') :
def __init__(self,bot):
self.bot = bot
#commands.Cog.listener()
async def on_message(self, message):
if message.guild:
db = sqlite3.connect('main.sqlite')
cursor = db.cursor()
cursor.execute(
f"SELECT * FROM main WHERE guild_id = ?", (message.guild.id, ))
result = cursor.fetchone()
if result:
channel = self.bot.get_channel(result[2])
role = message.guild.get_role(result[1])
if role:
if message.channel == channel:
if len(message.mentions) >= 3:
await message.add_reaction(emoji="<a:tick:748476262640779276>")
user = message.author
await user.add_roles(role)
await self.bot.process_commands(message)
#commands.group(invoke_without_command=True)
async def scrimsmod(self,ctx):
await ctx.send('Available Setup Commands: \nscrimsmod channel <#channel>\nscrimsmod role <message>')
#scrimsmod.command()
async def channel(self, ctx, channel:discord.TextChannel):
if ctx.message.author.guild_permissions.manage_messages:
db = sqlite3.connect('main.sqlite')
cursor = db.cursor()
cursor.execute(f"SELECT channel_id FROM main WHERE guild_id = {ctx.guild.id}")
result = cursor.fetchone()
if result is None:
sql = ("INSERT INTO main(guild_id, channel_id) VALUES(?,?)")
val = (ctx.guild.id, channel.id)
await ctx.send(f" Default Registration Channel has been set to {channel.mention}")
elif result is not None:
sql = ("UPDATE main SET channel_id = ? WHERE guild_id = ?")
val = (channel.id, ctx.guild.id)
await ctx.send(f"Default Registration Channel has been updated to {channel.mention}")
cursor.execute(sql, val)
db.commit()
cursor.close()
db.close()
#scrimsmod.command()
async def role(self, ctx,role: discord.Role):
if ctx.message.author.guild_permissions.manage_messages:
db = sqlite3.connect('main.sqlite')
cursor = db.cursor()
cursor.execute(f"SELECT role FROM main WHERE guild_id = {ctx.guild.id}")
result = cursor.fetchone()
if result is None:
sql = ("INSERT INTO main(guild_id, role) VALUES(?,?)")
val = (ctx.guild.id, role.id)
await ctx.send(f"Default role to give on correct registration have been set to `{role}`")
elif result is not None:
sql = ("UPDATE main SET role = ? WHERE guild_id = ?")
val = (role.id, ctx.guild.id)
await ctx.send(f"Default role to give on correct registration have been updated to `{role}`")
cursor.execute(sql, val)
db.commit()
cursor.close()
db.close()
Well, I believe the code is fine it doesn't throw any error . The issue is that it perfectly take input without any error but doesn't store it into db.
I believe the problem is with my db,
code -
db = sqlite3.connect('main.sqlite',timeout=10)
cursor = db.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS main(
guild_id INTEGER,
role INTEGER,
channel_id INTEGER
)
''')
I believe the problem is here , I should take TEXT in place INTEGER but I am not sure. Answer me with What do you I am doing wrong and how should I fix that.
I answered your last question with assuming that role and channel is TEXT type in the table and that answer should've solve your problem. If you change them, it will work fine and also it will be more efficient if you define the database variable and cursor on top of the on_message event just for once. So you don't have do connect to the database everytime.

Why Python MySQL Insert into the table not working?

I am using aiomysql and MariaDB. I can create a table or select data, but I can't insert data into the table. If you SELECT data with fetchall(), then it will show what you just inserted, but immediately delete from the database.
async def test_example(loop):
pool = await aiomysql.create_pool(host='127.0.0.1', port=3306,
user='root', password='',
db='test', loop=loop)
async with pool.acquire() as conn:
async with conn.cursor() as cur:
await cur.execute("INSERT INTO `tbl`(`id`, `val`) VALUES (37, 'z');")
print(cur.fetchall())
pool.close()
await pool.wait_closed()
loop = asyncio.get_event_loop()
loop.run_until_complete(test_example(loop))
Why?
From the PEP-249 specification:
.fetchall()
Fetch all (remaining) rows of a query result, returning them as a sequence of sequences (e.g. a list of tuples).
Since sql INSERT statement doesn't produce a result set you should try a SELECT statement before trying to obtain information from the database server.
Remove the quotes from the table and column names.
import aiomysql
import asyncio
async def select(loop, sql, pool):
async with pool.acquire() as conn:
async with conn.cursor() as cur:
await cur.execute(sql)
r = await cur.fetchone()
print(r)
async def insert(loop, sql, pool):
async with pool.acquire() as conn:
async with conn.cursor() as cur:
await cur.execute(sql)
await conn.commit()
async def main(loop):
pool = await aiomysql.create_pool(host='127.0.0.1', port=3306,
user='root', password='',
db='test', loop=loop)
c1 = select(loop=loop, sql='select * from tbl', pool=pool)
c2 = insert(loop=loop, sql="INSERT INTO tbl(id, val) VALUES (37, 'z');", pool=pool)
tasks = [asyncio.ensure_future(c1), asyncio.ensure_future(c2)]
return await asyncio.gather(*tasks)
if __name__ == '__main__':
cur_loop = asyncio.get_event_loop()
cur_loop.run_until_complete(main(cur_loop))

Why will this not send to the channel?

So, I want to send the warn to the user-set log channel, but it says NoneType has no attribute send. But, thing is
it's CLEARLY defined!
EDIT: I found out why. It's selecting the first query it gets from the database, so if the server_id from a different server's table is above another table, it won't work, because you're not in that server
cursor.execute("SELECT channel_id FROM logchannel")
result1 = cursor.fetchone()
if result1 is not None:
channel_id = result1[0]
channel1 = discord.utils.get(server.channels, id=channel_id)
await ctx.send(f"{member.mention} was warned for {reason}")
channel_id = result1[0]
channel1 = discord.utils.get(server.channels, id=channel_id)
await channel1.send(f"{member.mention} has been warned by {user.mention} for {reason}.")
cursor.execute('SELECT warnings FROM warninglog WHERE user_id = %s AND guild_id = %s', (user_id, guild_id))
result = cursor.fetchone()
if result is None:
warnings = 1
cursor.execute('INSERT INTO warninglog (warnings, name, user_id, guild_id ) VALUES (%s, %s, %s, %s)',
(warnings, name, user_id, guild_id))
conn.commit()

Optimizing Python SQL result loop

I have the following working code:
async def loadnames(self, ctx):
"""
Load the current alias of every member in the database.
Useful after joining a new guild or after a long downtime.
"""
async with ctx.message.channel.typing():
message = await ctx.send(f'Adding members to the database... {ctx.author.mention}')
db = await aiomysql.connect(host=globals.mysql_host,user=globals.mysql_user,password=globals.mysql_passwd,db=globals.mysql_db)
cur = await db.cursor()
#await cur.execute("SELECT discord,name FROM aliases WHERE discord=%s AND name=%s", (member.id,str(member)))
#result = await cur.fetchall()
count = 0
for member in ctx.guild.members:
try:
result = await cur.execute("SELECT * FROM aliases WHERE discord=%s AND name=%s", (member.id,str(member)))
if result == 0:
count = count + 1
await cur.execute("INSERT INTO aliases (discord,name) VALUES(%s,%s)", (member.id,str(member)))
await db.commit()
except:
await ctx.send(f'Error adding `{member}`! {ctx.author.mention}')
await cur.close()
db.close()
await message.edit(content=f'{count} members added to the database! {ctx.author.mention}')
Now, this works perfectly fine, the only issue I see with this is that I'm executing a new SELECT query for every iteration of my loop. So I'd like to put the SELECT statement outside of the loop, using cur.fetchall() to put it in a list (see commented lines in the code above), but I don't know after that how to check if a pair of (member.id,str(member)) is in my results.
Consider a single insert-select query in loop using the well know duplicate avoidance in SQL: NOT IN vs. NOT EXISTS vs. LEFT JOIN / IS NULL. Below runs the LEFT JOIN / IS NULL approach:
for member in ctx.guild.members:
try:
sql = """INSERT INTO aliases (discord, name)
SELECT a1.discord, a1.name
FROM aliases a1
LEFT JOIN aliases a2
ON a1.discord = a2.discord
AND a1.name = a2.name
AND a1.discord=%s AND a2.name=%s
WHERE a2.name IS NULL AND a2.discord IS NULL
"""
result = await cur.execute(sql, (member.id, str(member)))
count = cur.rowcount
await db.commit()
except:
await ctx.send(f'Error adding `{member}`! {ctx.author.mention}')
Even better, populate member IDs in a temp table and join to above query for only one query for all member IDs. SQL's set-based processing beats out application layer looping!
# CLEAN AND POPULATE TEMP TABLE
await cur.execute("DELETE FROM mytempTable")
members_list = [(member.id, str(member)) for member in ctx.guild.members]
await cur.executemany("INSERT INTO mytempTable (discord, name) VALUES (%s, %s)",
members_list)
await db.commit()
# ONLY ONE INSERT QUERY
sql = """INSERT INTO aliases (discord, name)
SELECT a1.discord, a1.name
FROM aliases a1
INNER JOIN mytempTable t
ON a1.discord = t.discord
AND a1.name = t.name
LEFT JOIN aliases a2
ON a1.discord = a2.discord
AND a1.name = a2.name
WHERE a2.name IS NULL AND a2.discord IS NULL
"""
result = await cur.execute(sql, (member.id, str(member)))
count = cur.rowcount
await db.commit()
Derived from Parfait's answer, I ended up with this:
async def loadnames(self, ctx):
"""
Load the current alias of every member in the database.
Useful after joining a new guild or after a long downtime.
"""
async with ctx.message.channel.typing():
count = 0
message = await ctx.send(f'Adding members to the database... {ctx.author.mention}')
db = await aiomysql.connect(host=globals.mysql_host,user=globals.mysql_user,password=globals.mysql_passwd,db=globals.mysql_db)
cur = await db.cursor()
await cur.execute("SELECT discord, name FROM aliases")
result = await cur.fetchall()
new_member_list = [(member.id, str(member)) for member in ctx.guild.members]
member_list = [(member[0],member[1]) for member in result]
diff_list = [member for member in new_member_list if member not in member_list]
count = await cur.executemany("INSERT INTO aliases (discord, name) VALUES (%s, %s)", diff_list)
await db.commit()
await cur.close()
db.close()
if count is None: count = 0
await message.edit(content=f'{count} members added to the database! {ctx.author.mention}')
Only one SELECT query at the beginning, then we play with the different lists to get the executemany INSERT statement which will do only the required number of insertions.

Categories