I'm new to Python. I've just created a sqlite3 db wrapper class to handle the database interaction in my application. My question is how do I return success/failure messages from my db wrapper class methods, addRow, updRow (which add and update rows) to the invoking program?
Here's the class code I cobbled together so far:-
class dbManager(object):
def __init__(self, db):
self.conn = lite.connect(db)
self.conn.execute('pragma foreign_keys = on')
self.conn.execute('pragma synchronous=off')
self.conn.commit()
self.cur = self.conn.cursor()
def query(self, arg):
self.cur.execute(arg)
self.conn.commit()
return self.cur
def addRow(self, tablename, data):
"""
Insert data into a table. The data does not have to be escaped.
"""
global actInserts
# Create a new cursor
# tc = self.conn.cursor() # opened at init
tablelist = ""
valueholder = ""
valuelist = []
for key, value in data.items():
if len(tablelist) > 0:
tablelist += ', '
valueholder += ', '
# Add to table column list
tablelist += key
# Add a holder
valueholder += '?'
# build the insert values
valuelist.append(value)
# Perform and commit the insert
try:
dbResponse = self.cur.execute("INSERT INTO " + tablename + " (" + tablelist + ") VALUES (" + valueholder + ");", valuelist)
actInserts += 1
except lite.Error, e:
dbResponse = 'Sqlite Error NP: ' + e.args[0]
print 'Sqlite Error NP: ' + e.args[0]
return dbResponse
def closeConnection (self):
self.conn.commit()
self.conn.close()
def __del__(self):
self.conn.close()
I think there are 2 ways you can do this. You can:
Change the return type of you function to something like a tuple of (error_code, SQL_results), or
Throw an exception if the query fails (or don't catch the one you already are handling) and leave the exception handling logic to the user (calling program).
I think option 2 is the better way.
Also, I think your query building would be more clear (or at least more concise) using something like the following (assumes that data is a dictionary)
tablelist = ','.join(list(data.viewkeys()))
valueholder = ','.join(['?' for i in data.viewkeys()])
valuelist = list(data.viewvalues())
Related
I have a class which contains functions that connect to snowflake (using snowflake connector) and perform data cleaning. My thought was to create multiple functions to do separate data cleaning.
I've defined one variable in a function called "work_data" called "self.calls" and I've defined that function as a pandas dataframe and I'd like to access that dataframe in another function.
Here's my current process:
I first initialized the variable in the init function and made it a blank list for now. After I've run the "work_data" function and try and run the "finalized_data" function..but I get the blank list instead of the panadas data frame.
Here's my code so far:
class SnowReader:
sql_pslink ='select * from xyz in xyz";'
sql_account= 'select * from xyz in xyz;'
sql_call = 'select * from xyz in xyz;'
sql_sales = 'select * from xyz in xyz;'
sql_address = 'select * from xyz in xyz;'
def __init__(self) -> None:
self.database = "xyz"
self.username = INTEGRATION_USER
self.password = SNOW_PASSWORD
self.account = "xyz"
self.warehouse = "xyz"
self.role = "xyz"
self.schema = "xyz"
self.conn = self._connect_snow()
self.calls = []
def _connect_snow(self):
try:
self.conn = snowflake.connector.connect(
user=self.username,
password=self.password,
account=self.account,
warehouse=self.warehouse,
database=self.database,
role=self.role,
schema=self.schema,
)
logger.info("You are connected to Snowflake")
except Exception as ex:
if ex.errno == 250001:
logger.error(
f"Invalid username/password, please re-enter username and password.."
)
def work_data(self):
if not self.conn:
self._connect_snow()
link = pd.read_sql(SnowReader.sql_pslink, self.conn)
account = pd.read_sql(SnowReader.sql_account, self.conn)
self.calls = pd.read_sql(SnowReader.sql_call, self.conn)
sales = pd.read_sql(SnowReader.sql_sales, self.conn)
Calls_merged = self.calls.groupby('xyz', as_index=False)['xyz'].count()
Account_step1 = Calls_merged.merge(account,left_on='xyz', right_on='xyz', how="left" )
Sales_merged = sales.groupby(['xyz'], as_index=False)['xyz'].count()
Account_final = Sales_merged.merge(Account_step1,left_on='xyz', right_on='xyz', how="left" )
Master_Address = pd.read_sql(SnowReader.sql_address, self.conn)
return Account_final, Master_Address, link, self.calls
def finalize_data(self):
return self.calls
a, b, c, d= SnowReader().work_data()
display(a,b,c, d)
testt = SnowReader().finalize_data()
testt
I want to create a class with methods, so that I do not have to create multiple functions.
Below is my code, I want to get a class with two methods. Method 1: SQL Query, method 2: sql insert.
Any tipp is greatly appreciated.
Stefan
def dbconnect():
dbconn = pymysql.connect(host='192.168.1.2', port=3307, user='username', passwd='password', db='dbname')
try:
cur = dbconn.cursor()
sqlQuery = "select * from data"
sqlQuerygetlast = "SELECT * FROM data ORDER BY id DESC LIMIT 1"
sqlQuerygetlast10 = "SELECT * FROM data ORDER BY id DESC LIMIT 10"
cur.execute(sqlQuerygetlast10)
rows = cur.fetchall()
for row in rows:
print(row)
except Exception as e:
print("Exeception occured:{}".format(e))
finally:
#dbconn.commit()
dbconn.close()
My objective is to call the methods from my code, i.e. query a select statement.
Thanks a lot
Stefan
I guess you mean that you don't want to create multiple connections?
Then you should implement it as a Context manager:
class DB:
def __init__(self):
self.dbconn = None
def get_last(self, n)
try:
cur = self.dbconn.cursor()
sqlQuerygetlast = "SELECT * FROM data ORDER BY id DESC LIMIT {}".format(n)
cur.execute(sqlQuerygetlast)
rows = cur.fetchall()
for row in rows:
print(row)
except Exception as e:
print("Exeception occured:{}".format(e))
finally:
# self.dbconn.commit()
def some_other_method(self):
self.dbconn.do_something()
def __enter__(self):
self.dbconn = pymysql.connect(
host='192.168.1.2',
port=3307,
user='username',
passwd='password',
db='dbname'
)
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
dbconn.close()
return True
and use it as follows:
with DB() as db:
db.get_last(1)
db.get_last(10)
db.some_other_method()
This will create only one instance of a database connection and close after it is finished.
Writing a class in Python is fairly simple.
Here's an example of how to write the classic Person class and how to use properties and methods.
Specifically, the presentation method is also making use of the name property.
From this example you can move on and build your Database class implementation quite easily:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def presentation(self):
print("Hello my name is " + self.name)
p1 = Person("John", 36)
p1.presentation()
Remember that in all the methods of a class you will have to specify the reserved keyword self as parameter (plus, clearly, any other parameter you might need).
This question already has answers here:
Is there a way to get a schema of a database from within python?
(10 answers)
Closed 6 years ago.
class DatabaseManager(object): ##Ignore the indentation##
def __init__(self, db):
self.conn = sqlite3.connect(db)
self.conn.execute('pragma foreign_keys = on')
self.conn.commit()
self.c = self.conn.cursor()
def query_params(self, arg, params=None):
self.c.execute(arg, params)
self.conn.commit()
return self.c
def query(self, arg):
self.c.execute(arg)
self.conn.commit()
return self.c
def fetch(self):
self.c.fetchall()
self.conn.commit()
return self.c
def __del__(self):
self.conn.close()
dbmgr = DatabaseManager("connect.db")
Other code
....
....
....
and then:
def read_from_db():
tables = []
query = "SELECT name FROM sqlite_master WHERE type='table'"
dbmgr.query(query)
rows = dbmgr.fetch()
for row in rows:
tables.append(row)
print (tables)
I am not able to figure out how to view the tables in my database, they are real as I can see them in my sqlite manager. This used to work when I did not have the class and just did:
def read_from_db():
tables = []
c.execute("SELECT name FROM sqlite_master WHERE type='table'")
rows = c.fetchall()
for row in rows:
tables.append(row)
print (tables)
So, you fetch the result, but never do anything with it, then return the spent cursor...
def fetch(self):
self.c.fetchall() # this returns a list of your data
self.conn.commit()
return self.c
It's also not clear what you are committing in the function
Compare to your other code,
rows = c.fetchall()
So you should only need
def fetch(self):
return self.c.fetchall()
Then call it
tables = dbmgr.fetch()
Or, alternatively
tables = dbmgr.query(query).fetchall()
Note that you are making an empty list, then adding elements of a list to that list. You can shortcut that process by assigning tables directly
We're working on a text-based game (MUD) and have hit this roadblock.
The code:
class RoomMove():
def __init__(self):
self.room = 1
self.name = 'Lorinth'
self.moveRooms()
self.updateRoom()
[extra code doesn't matter]
def updateRoom(self):
global c
room = str(self.room)
room = (room)
print room
while room > 0:
c.execute("""SELECT * FROM RoomPlayers where ID=?""", room)
spaceCheck = c.fetchone()
print spaceCheck
counter = 1
if spaceCheck[counter] not in ('', None):
counter += 1
else:
room = self.room
name = self.name
c.execute("""UPDATE RoomPlayers SET '1'=? WHERE ID=?""", (name, room))
conn.commit()
it throws back this error:
c.execute("""SELECT * FROM RoomPlayers where ID=?""", room)
ValueError: parameters are of unsupported type
EDITS: I've tried it with (room, ) ..no difference in the error though.
Any ideas?
Thanks!
As you can read in the documentation:
fetchone()
Fetches the next row of a query result set, returning a single sequence, or None when no more data is available.
If there is no more data c.fetchone() assigns None to the spaceCheck variable, hence TypeError when you try to access spaceCheck[counter].
Try something like that:
if spaceCheck is not None:
counter += 1
UPDATE
You should replace
room = (room)
with:
room = (room, )
Another update
Assuming you create table like below:
sqlite> CREATE TABLE RoomPlayers (ID numeric, player_name VARCHAR);
Your code could like similar to this:
class RoomMove():
def __init__(self, room, name):
self.room = room
self.name = name
self.con = None
self.db_path = "./foo.sqlite"
def updateRoom(self):
try:
# Globals are evil. Don't use them
self.con = sqlite3.connect(self.db_path)
c = con.cursor()
# comparing tuple or string to number dosen't make sense. Compare original number
# You should check if room is valid inside constructor
# while room > 0 would probably lead to indefinite loop
if rooom > 0:
#You don't have to cast room number to string
c.execute("""SELECT id FROM RoomPlayers where ID=?""", (self.room, ))
spaceCheck = c.fetchone()
# Your counter takes only two values so you can do it like that
counter = 2 if spaceCheck is not None else 1
if counter == 1:
# Don't name columns '1' - it is really bad idea
# Like above - coneverting room and name to string is pointlees
c.execute("""UPDATE RoomPlayers SET 'player_name'=? WHERE ID=?""", (self.name, self.room))
self.con.commit()
finally:
# No matter what happens close connection
if self.con is not None:
self.con.close()
What fixed the problem shown above:
sqlite3 doesn't read integers, just strings. (not actually true)
So I made the integer be read as a string in the else loop.
here:
else:
room = self.room
name = self.name
c.execute("""UPDATE RoomPlayers SET '1'=? WHERE ID=?""", (name, room))
room is being passed to c.execute as an integer, the solution?
else:
room = str(self.room)
....
Took forever to see that!
Is there a way to force Django models to pass a field to a MySQL function every time the model data is read or loaded? To clarify what I mean in SQL, I want the Django model to produce something like the following:
On model load: SELECT AES_DECRYPT(fieldname, password) FROM tablename
On model save: INSERT INTO tablename VALUES (AES_ENCRYPT(userinput, password))
Instead of on model load, you can create a property on your model, and when the property is accessed, it can read the database:
def _get_foobar(self):
if not hasattr(self, '_foobar'):
cursor = connection.cursor()
self._foobar = cursor.execute('SELECT AES_DECRYPT(fieldname, password) FROM tablename')[0]
return self._foobar
foobar = property(_get_foobar)
Now after loading, you can refer to mything.foobar, and the first access will retrieve the decryption from the database, holding onto it for later accesses.
This also has the advantage that if some of your code has no use for the decryption, it won't happen.
I would define a custom modelfield for the column you want encrypted/decrypted. Override the to_python method to run the decryption when the model is loaded, and get_db_prep_value to run the encryption on saving.
Remember to set the field's metaclass to models.SubfieldBase otherwise these methods won't be called.
Here is a working solution, based in part on (http://www.djangosnippets.org/snippets/824/):
class Employee(models.Model):
social_security_number = models.CharField(max_length=32)
def _get_ssn(self):
cursor = connection.cursor()
cursor.execute("SELECT AES_DECRYPT(UNHEX(social_security_number), %s) as ssn FROM tablename WHERE id=%s", [settings.SECRET_KEY, self.id])
return cursor.fetchone()[0]
def _set_ssn(self, ssn_value):
cursor = connection.cursor()
cursor.execute("SELECT HEX(AES_ENCRYPT(%s, %s)) as ssn", [ssn_value, settings.SECRET_KEY])
self.social_security_number = cursor.fetchone()[0]
ssn = property(_get_ssn, _set_ssn)
And the results:
>>> from foo.bar.models import Employee
>>> p=Employee.objects.create(ssn='123-45-6789')
>>> p.ssn
'123-45-6789'
mysql> select * from foo_employee;
+----+----------------------------------+
| id | social_security_number |
+----+----------------------------------+
| 31 | 41DF2D946C9186BEF77DD3307B85CC8C |
+----+----------------------------------+
1 row in set (0.00 sec)
It's definitely hackish, but it seems Django won't let you do it any other way at the moment. It's also worth noting that to_python will be called every time you change the value in python in addition to when it is first loaded.
from django.db import connection, models
import re
class EncryptedField(models.TextField):
__metaclass__ = models.SubfieldBase
def to_python(self, value):
if not re.match('^*some pattern here*$', value):
cursor = connection.cursor()
cursor.execute('SELECT AES_DECRYPT(%s, %s)', [value, settings.SECRET_KEY])
return cursor.fetchone()[0]
return value
def get_db_prep_value(self, value):
cursor = connection.cursor()
cursor.execute('SELECT AES_ENCRYPT(%s, %s)', [value, settings.SECRET_KEY])
return cursor.fetchone()[0]
class Encrypt(models.Model):
encrypted = EncryptedField(max_length = 32)
After deep search in the implementation of Django ORM,
I found that it can be solved by something like this:
class EncryptedField(models.BinaryField):
#staticmethod
def _pad(value):
return value + (AES.block_size - len(value) % AES.block_size) * b('\x00')
def _encrypt(self, data):
if not data:
return None
return self.cipher.encrypt(self._pad(data.encode('utf8')))
def _decrypt(self, data):
if not data:
return None
return self.cipher.decrypt(force_bytes(data)).rstrip(b'\x00').decode('utf8')
#property
def cipher(self):
return AES.new(KEY, mode=AES.MODE_CBC, IV=self._iv)
def get_db_prep_value(self, value, connection, prepared=False):
if value is not None:
value = self._encrypt(value)
if value:
value = binascii.hexlify(value)
return value
def get_placeholder(self, value, compiler, connection):
return 'unhex(%s)'
Using Django signals you can do stuff when a model instance is saved, but as far as I know you can't trigger anything on read.
EDIT: My bad, it seems you can do stuff when initializing a model instance.