Skip to content

Commit 8386d1f

Browse files
committed
Major restructuring: broke OracleClient.py into several files.
SQL for all db types now work, with & without cursors. sql command-line clients incomplete, commented out. Data dictionary stuff incomplete, commented out. DBInstance code much more modular, much cleaner.
1 parent 5518de1 commit 8386d1f

File tree

10 files changed

+1568
-1124
lines changed

10 files changed

+1568
-1124
lines changed

DBClient.py

Lines changed: 624 additions & 0 deletions
Large diffs are not rendered by default.

DBInstance.py

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
""" DBInstance.py """
2+
3+
from MyConstants import *
4+
from MyFunctions import *
5+
6+
7+
class DBInstance(object):
8+
""" Class containing database connection utilities and information
9+
about one database instance (referred to as "this database").
10+
11+
Attributes:
12+
db_type (str): the type of database (Oracle, SQL Server, etc).
13+
db_path (str): the path of the database file, for SQLite and Access.
14+
username (str): a username for connecting to this database.
15+
password (str): the password for "username".
16+
hostname (str): the hostname of this database.
17+
port_num (int): the port this database listens on.
18+
instance (str): the name of this database instance.
19+
db_lib_obj (object): imported db library object.
20+
db_lib_name (str): name of the imported db library.
21+
db_lib_version (str): version of the imported db library.
22+
db_software_version (str): version of the database software being used.
23+
connection: the handle to this database. I set connection = None when
24+
connection closed, this is not default behavior.
25+
callers (dict): collection of DBClient instances using this instance of
26+
of DBInstance, along with their cursor objects.
27+
"""
28+
def __init__(self, db_type: str, db_path: str, username: str, password: str,
29+
hostname: str, port_num: int, instance: str) -> None:
30+
""" Constructor method for this class.
31+
32+
Parameters:
33+
db_type (str): the type of database (Oracle, SQL Server, etc).
34+
db_path (str): the path of the database file, for SQLite and Access.
35+
username (str): the username for the connection to this database.
36+
password (str): the password for "username".
37+
hostname (str): the hostname of this database.
38+
port_num (int): the port this database listens on.
39+
instance (str): the name of this database instance.
40+
Returns:
41+
"""
42+
# Save arguments of __init__.
43+
self.db_type: str = db_type
44+
self.db_path: str = db_path
45+
self.username: str = username
46+
self.password: str = password
47+
self.hostname: str = hostname
48+
self.port_num: int = port_num
49+
self.instance: str = instance
50+
51+
# Check if db_type valid.
52+
if self.db_type not in db_types:
53+
print('Invalid database type "{}".'.format(self.db_type))
54+
exit(1)
55+
56+
# Import appropriate database library.
57+
self.db_lib_name = lib_name_for_db[self.db_type]
58+
self.db_lib_obj = __import__(self.db_lib_name)
59+
60+
# Get database library version.
61+
if self.db_lib_name in {psycopg2, pymysql}:
62+
self.db_lib_version = self.db_lib_obj.__version__
63+
else:
64+
self.db_lib_version = self.db_lib_obj.version
65+
z = 'Using {} library version {}.'
66+
print(z.format(self.db_lib_name, self.db_lib_version))
67+
68+
# Get primary parameter style.
69+
print('Parameter style "{}".'.format(self.db_lib_obj.paramstyle))
70+
# paramstyle = 'named': cx_Oracle. Option for sqlite3 & psycopg2.
71+
# paramstyle = 'qmark': sqlite3 and pyodbc.
72+
# paramstyle = 'pyformat': pymysql and psycopg2.
73+
74+
# Connect to database instance.
75+
self.connection = None
76+
try:
77+
if db_type in uses_connection_string:
78+
z = self.get_db_connection_string()
79+
self.connection = self.db_lib_obj.connect(z)
80+
else:
81+
args = (self.hostname, self.username, self.password,
82+
self.instance, self.port_num)
83+
self.connection = self.db_lib_obj.connect(*args)
84+
print('Successfully connected to database.')
85+
except self.db_lib_obj.Error:
86+
print_stacktrace()
87+
print('Failed to connect to database.')
88+
exit(1)
89+
90+
# Get database software version.
91+
self.db_software_version = self.get_db_software_version()
92+
93+
# Prepare to save cursors for this connection.
94+
self.callers = dict()
95+
96+
return
97+
# End of method __init__.
98+
99+
# METHODS INVOLVING THE DATABASE CONNECTION.
100+
101+
def close_connection(self, del_cursors: bool = False) -> None:
102+
""" Method to close connection to this database.
103+
104+
Parameters:
105+
del_cursors (bool): if True, first delete dependent cursors,
106+
if False, refuse to close connection.
107+
Returns:
108+
"""
109+
if del_cursors:
110+
# TODO need to test with multiple DBClients.
111+
for caller, cursor in self.callers.items():
112+
self.delete_cursor(caller)
113+
del caller
114+
else:
115+
print('Dependent DBClients exist, will not close connection.')
116+
117+
if self.db_type in file_databases:
118+
z = '\n{} from database at "{}".'
119+
z = z.format('{}', self.db_path)
120+
else:
121+
z = '\n{} from instance "{}" on host "{}".'
122+
z = z.format('{}', self.instance, self.hostname)
123+
try:
124+
self.connection.close()
125+
self.connection = None
126+
print(z.format('Successfully disconnected'))
127+
except self.db_lib_obj.Error:
128+
print_stacktrace()
129+
print(z.format('Failed to disconnect'))
130+
exit(1)
131+
return
132+
# End of method close_connection.
133+
134+
def create_cursor(self, caller):
135+
""" Method that creates and returns a new cursor. Saves caller
136+
object, along with its cursor, into "callers", so that deletion of
137+
self cannot be done before dependent callers and their cursors are
138+
deleted.
139+
140+
Parameters:
141+
caller: the self object of the object calling create_cursor, added
142+
to pool of caller objects.
143+
Returns:
144+
cursor: handle to this database.
145+
"""
146+
cursor = self.connection.cursor()
147+
self.callers[caller] = cursor
148+
return cursor
149+
# End of method create_cursor.
150+
151+
def delete_cursor(self, caller):
152+
""" Method that deletes an existing cursor.
153+
154+
Parameters:
155+
caller: the DBClient object calling delete_cursor.
156+
Remove from pool of caller objects.
157+
Returns:
158+
"""
159+
# Close cursor.
160+
self.callers[caller].close()
161+
# Delete caller DBClient from callers pool.
162+
del self.callers[caller]
163+
return
164+
# End of method delete_cursor.
165+
166+
# DATABASE INFORMATION METHODS.
167+
168+
def get_connection_status(self) -> str:
169+
""" Method that prints whether or not connected to this database.
170+
171+
Parameters:
172+
Returns:
173+
"""
174+
if self.db_type in file_databases:
175+
z = 'Connection status for the database at "{}": {}connected.'
176+
z = z.format(self.db_path, '{}')
177+
else:
178+
z = 'Connection status for instance "{}", host "{}": {}connected.'
179+
z = z.format(self.instance, self.hostname, '{}')
180+
if self.connection is not None:
181+
z = z.format('')
182+
else:
183+
z = z.format('not ')
184+
return z
185+
# End of method get_connection_status.
186+
187+
def get_db_lib_name(self) -> str:
188+
""" Method to return the name of the needed database library.
189+
190+
Parameters:
191+
Returns:
192+
db_lib_name (str): database library name.
193+
"""
194+
return self.db_lib_name
195+
# End of method get_db_lib_name.
196+
197+
def get_db_type(self) -> str:
198+
""" Method to return the database type.
199+
200+
Parameters:
201+
Returns:
202+
db_type (str): database software type.
203+
"""
204+
return self.db_type
205+
# End of method get_db_type.
206+
207+
def get_db_software_version(self) -> str:
208+
""" Method to return the database software version.
209+
210+
Parameters:
211+
Returns:
212+
db_software_version (str): database software version.
213+
"""
214+
sql = 'Nada'
215+
if self.db_type in {postgresql, mysql}:
216+
sql = 'SELECT version()'
217+
elif self.db_type == sql_server:
218+
sql = 'SELECT @@VERSION'
219+
elif self.db_type == sqlite:
220+
sql = 'select sqlite_version()'
221+
elif self.db_type == oracle:
222+
sql = "SELECT * FROM v$version WHERE banner LIKE 'Oracle%'"
223+
224+
if sql != 'Nada':
225+
cursor = self.connection.cursor()
226+
cursor.execute(sql)
227+
version = cursor.fetchone()[0]
228+
cursor.close()
229+
del cursor
230+
elif self.db_type == access:
231+
version = "There's no way to get the MS Access Version through SQL."
232+
elif self.db_lib_name != pyodbc:
233+
version = self.connection.version
234+
else:
235+
version = 'Unknown'
236+
return str(version)
237+
# End of method get_db_software_version.
238+
239+
def get_db_connection_string(self):
240+
""" Method to form database connection string.
241+
242+
Parameters:
243+
Returns:
244+
db_software_version (str): database software version.
245+
"""
246+
z = ''
247+
if self.db_lib_name == pymysql:
248+
z = ''
249+
elif self.db_lib_name == cx_Oracle:
250+
z = '{}/{}@{}:{}/{}'
251+
elif self.db_lib_name == postgresql:
252+
z = "user='{}' password='{}' host='{}' port='{}' dbname='{}'"
253+
elif self.db_lib_name == pyodbc:
254+
if self.db_type == access:
255+
z = ('DRIVER={{Microsoft Access Driver (*.mdb, *.accdb)}};'
256+
'DBQ={};')
257+
elif self.db_type == sql_server:
258+
z = ('DRIVER={{SQL Server}};UID={};PWD={};SERVER={};PORT={};'
259+
'DATABASE={}')
260+
elif self.db_lib_name == sqlite3:
261+
z = '{}'
262+
else:
263+
print('Unknown db library {}, aborting.'.format(self.db_type))
264+
exit(1)
265+
266+
if self.db_type in {sql_server, oracle, postgresql}:
267+
z = z.format(self.username, self.password, self.hostname,
268+
self.port_num, self.instance)
269+
elif self.db_type in file_databases:
270+
z = z.format(self.db_path)
271+
return z
272+
# End of method get_db_software_version.
273+
274+
def print_all_connection_parameters(self) -> None:
275+
""" Method that executes all print methods of this class.
276+
277+
Parameters:
278+
Returns:
279+
"""
280+
print('The database type is "{}".'.format(self.db_type))
281+
version = self.get_db_software_version()
282+
print('The database software version is {}.'.format(version))
283+
if self.db_type in file_databases:
284+
print('The database path is "{}".'.format(self.db_path))
285+
else:
286+
print('The database username is "{}".'.format(self.username))
287+
print('The database hostname is "{}".'.format(self.hostname))
288+
print('The database port number is {}.'.format(self.port_num))
289+
print('The database instance is "{}".'.format(self.instance))
290+
print(self.get_connection_status())
291+
return
292+
# End of method print_all_connection_parameters.
293+
# End of Class DBInstance.

MyConstants.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
""" MyConstants.py """
2+
3+
# Supported database types.
4+
oracle = 'oracle'
5+
mysql = 'mysql'
6+
sql_server = 'sql server'
7+
postgresql = 'postgresql'
8+
access = 'access'
9+
sqlite = 'sqlite'
10+
db_types = [oracle, mysql, sql_server, postgresql, access, sqlite]
11+
12+
# Groupings of database types.
13+
uses_connection_string = set(db_types) - {mysql}
14+
file_databases = {access, sqlite}
15+
16+
# Database libraries.
17+
cx_Oracle = 'cx_Oracle'
18+
pymysql = 'pymysql'
19+
pyodbc = 'pyodbc'
20+
psycopg2 = 'psycopg2'
21+
sqlite3 = 'sqlite3'
22+
lib_name_for_db = {oracle: cx_Oracle, mysql: pymysql, sql_server: pyodbc,
23+
postgresql: psycopg2, access: pyodbc, sqlite: sqlite3}
24+
25+
# Parameterization/bind variable format used here.
26+
paramstyle_named = {cx_Oracle, sqlite3}
27+
paramstyle_pyformat = {pymysql, psycopg2}
28+
paramstyle_qmark = {pyodbc}

0 commit comments

Comments
 (0)