lukhed-basic-utils 1.6.2__tar.gz → 1.6.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/PKG-INFO +1 -1
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/githubCommon.py +3 -3
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/sqlCommon.py +927 -1
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils.egg-info/PKG-INFO +1 -1
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils.egg-info/requires.txt +1 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/setup.py +4 -3
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/LICENSE +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/README.md +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/__init__.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/chartJsCommon.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/classCommon.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/fileCommon.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/listWorkCommon.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/mathCommon.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibBarCharts.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibBasics.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibFormatting.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibLineCharts.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibPieCharts.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibScatter.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibSpecial.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/osCommon.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/requestsCommon.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/stringCommon.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/timeCommon.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils.egg-info/SOURCES.txt +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils.egg-info/dependency_links.txt +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils.egg-info/top_level.txt +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/setup.cfg +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/tests/test_osCommon.py +0 -0
- {lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/tests/test_timeCommon.py +0 -0
|
@@ -125,7 +125,7 @@ class GithubHelper:
|
|
|
125
125
|
i = input((f'ERROR: There is no project "{self.project}" in the config file. Would you like to go thru setup '
|
|
126
126
|
'to add a new Github key for this project name? (y/n): '))
|
|
127
127
|
if i == 'y':
|
|
128
|
-
self.
|
|
128
|
+
self._gh_guided_setup()
|
|
129
129
|
else:
|
|
130
130
|
print("Ok, exiting...")
|
|
131
131
|
quit()
|
|
@@ -133,7 +133,7 @@ class GithubHelper:
|
|
|
133
133
|
def _prompt_for_setup(self):
|
|
134
134
|
i = input("1. You do not have a valid config file to utilize github. Do you want to go thru easy setup? (y/n):")
|
|
135
135
|
if i == 'y':
|
|
136
|
-
self.
|
|
136
|
+
self._gh_guided_setup()
|
|
137
137
|
elif i == 'n':
|
|
138
138
|
print("OK, to use github functions, see https://github.com/lukhed/lukhed_basic_utils for more information.")
|
|
139
139
|
quit()
|
|
@@ -141,7 +141,7 @@ class GithubHelper:
|
|
|
141
141
|
print("Did not get an expected result of 'y' or 'n'. Please reinstantiate and try again. Exiting script.")
|
|
142
142
|
quit()
|
|
143
143
|
|
|
144
|
-
def
|
|
144
|
+
def _gh_guided_setup(self):
|
|
145
145
|
input(("\n2. Starting setup\n"
|
|
146
146
|
"The github key you provide in this setup will be stored locally only. "
|
|
147
147
|
f"After setup, you can see the config file in your specified destination {self._github_config_file}"
|
|
@@ -4,6 +4,9 @@ from mysql.connector.connection import MySQLConnection
|
|
|
4
4
|
import mysql.connector
|
|
5
5
|
import atexit
|
|
6
6
|
import json
|
|
7
|
+
import psycopg2
|
|
8
|
+
import atexit
|
|
9
|
+
from psycopg2 import sql
|
|
7
10
|
|
|
8
11
|
class SqlHelper(classCommon.LukhedAuth):
|
|
9
12
|
def __init__(self, datbase_project, datbase_name, key_management='github', auth_type='basic',
|
|
@@ -49,9 +52,9 @@ class SqlHelper(classCommon.LukhedAuth):
|
|
|
49
52
|
self.cursor = None
|
|
50
53
|
atexit.register(self.close_connection)
|
|
51
54
|
|
|
52
|
-
|
|
53
55
|
###################
|
|
54
56
|
# Auth and Connection
|
|
57
|
+
|
|
55
58
|
def _auth_setup(self):
|
|
56
59
|
"""
|
|
57
60
|
Set up authentication for the SQL database.
|
|
@@ -1249,11 +1252,934 @@ class SqlHelper(classCommon.LukhedAuth):
|
|
|
1249
1252
|
self.cursor.execute(alter_query)
|
|
1250
1253
|
self.db_connection.commit()
|
|
1251
1254
|
|
|
1255
|
+
class PostgresSqlHelper(classCommon.LukhedAuth):
|
|
1256
|
+
def __init__(self, datbase_project, datbase_name, key_management='github', auth_type='basic',
|
|
1257
|
+
auth_dict=None):
|
|
1258
|
+
"""
|
|
1259
|
+
Initializes the PostgresSqlHelper class for managing PostgreSQL database connections and operations.
|
|
1260
|
+
|
|
1261
|
+
This class mirrors the structure and behavior of the original MySQL-based SqlHelper but is adapted for use with
|
|
1262
|
+
PostgreSQL databases such as those hosted on Supabase. It provides support for table management, data insertion,
|
|
1263
|
+
querying, and connection handling using psycopg2.
|
|
1264
|
+
|
|
1265
|
+
Parameters
|
|
1266
|
+
----------
|
|
1267
|
+
datbase_project : str
|
|
1268
|
+
Name of the project or repository where the database is hosted. This class will use this to manage
|
|
1269
|
+
the authentication data going forward. Setup the authentication data for a project one time and then you
|
|
1270
|
+
can re-use it for all future connections to the database by utilizing the database project name.
|
|
1271
|
+
datbase_name : str
|
|
1272
|
+
Name of the PostgreSQL database to connect to. All queries and operations will be performed on this database.
|
|
1273
|
+
You can change the database later by using the `change_database` method.
|
|
1274
|
+
key_management : str, optional
|
|
1275
|
+
Options for storing your authentication data. 'local' to store your auth on your local hardware.
|
|
1276
|
+
'github' to store it in a private GitHub repository (you will need a GitHub account and GitHub token),
|
|
1277
|
+
by default 'github'
|
|
1278
|
+
auth_type : str, optional
|
|
1279
|
+
Currently supports 'basic' auth, which is authentication by 'host', 'user', and 'password', by default 'basic'
|
|
1280
|
+
auth_dict : dict, optional
|
|
1281
|
+
By default, this class will walk you through providing your authentication data to be stored according
|
|
1282
|
+
to your preference via command prompts. You can also provide the necessary dictionary via this
|
|
1283
|
+
parameter to skip the setup., by default None
|
|
1284
|
+
"""
|
|
1285
|
+
|
|
1286
|
+
super().__init__(datbase_project, key_management=key_management)
|
|
1287
|
+
|
|
1288
|
+
self.auth_type = auth_type
|
|
1289
|
+
self._auth_dict = auth_dict
|
|
1290
|
+
|
|
1291
|
+
self.database_project = datbase_project
|
|
1292
|
+
self.database_name = datbase_name
|
|
1293
|
+
|
|
1294
|
+
if self._auth_data is None:
|
|
1295
|
+
self._auth_setup()
|
|
1296
|
+
|
|
1297
|
+
self.db_connection = None
|
|
1298
|
+
self.cursor = None
|
|
1299
|
+
atexit.register(self.close_connection)
|
|
1300
|
+
|
|
1301
|
+
|
|
1302
|
+
###################
|
|
1303
|
+
# Auth and Connection
|
|
1304
|
+
def _auth_setup(self):
|
|
1305
|
+
if self.auth_type == 'basic':
|
|
1306
|
+
if self._auth_dict is None:
|
|
1307
|
+
input("Basic auth requires the following information: 'host', 'user', 'password', and 'port' "
|
|
1308
|
+
"Press enter to start inputting these values.")
|
|
1309
|
+
host = input("Enter host (e.g. db.xxxxxx.supabase.co): ")
|
|
1310
|
+
user = input("Enter user: ")
|
|
1311
|
+
password = input("Enter password: ")
|
|
1312
|
+
port = input("Enter port: ")
|
|
1313
|
+
|
|
1314
|
+
self._auth_data = {
|
|
1315
|
+
"host": host,
|
|
1316
|
+
"user": user,
|
|
1317
|
+
"password": password,
|
|
1318
|
+
"port": port
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
self.kM.force_update_key_data(self._auth_data)
|
|
1322
|
+
print("Basic auth data has been set up successfully.")
|
|
1323
|
+
else:
|
|
1324
|
+
raise ValueError(f"Unsupported auth_type: {self.auth_type}")
|
|
1325
|
+
|
|
1326
|
+
def _check_connect_db(self):
|
|
1327
|
+
if self.db_connection is None:
|
|
1328
|
+
self.connect()
|
|
1329
|
+
|
|
1330
|
+
def connect(self):
|
|
1331
|
+
self.db_connection = psycopg2.connect(
|
|
1332
|
+
host=self._auth_data['host'],
|
|
1333
|
+
user=self._auth_data['user'],
|
|
1334
|
+
password=self._auth_data['password'],
|
|
1335
|
+
dbname=self.database_name,
|
|
1336
|
+
port=self._auth_data['port']
|
|
1337
|
+
)
|
|
1338
|
+
self.cursor = self.db_connection.cursor()
|
|
1339
|
+
|
|
1340
|
+
def test_connection(self):
|
|
1341
|
+
self._check_connect_db()
|
|
1342
|
+
print(f"Connected to the database {self.database_name}")
|
|
1343
|
+
|
|
1344
|
+
def close_connection(self):
|
|
1345
|
+
if self.cursor:
|
|
1346
|
+
self.cursor.close()
|
|
1347
|
+
if self.db_connection:
|
|
1348
|
+
self.db_connection.close()
|
|
1349
|
+
print(f"Connection was closed to {self.database_name}")
|
|
1350
|
+
|
|
1351
|
+
def change_database(self, database_name):
|
|
1352
|
+
self.close_connection()
|
|
1353
|
+
self.database_name = database_name
|
|
1354
|
+
print("New database is: " + self.database_name)
|
|
1355
|
+
|
|
1356
|
+
def update_auth_data(self, force_new_auth_dict=None):
|
|
1357
|
+
if force_new_auth_dict is not None:
|
|
1358
|
+
self._auth_dict = force_new_auth_dict
|
|
1359
|
+
self._auth_setup()
|
|
1360
|
+
|
|
1361
|
+
|
|
1362
|
+
##################
|
|
1363
|
+
# Table Management
|
|
1364
|
+
def get_all_tables(self):
|
|
1365
|
+
self._check_connect_db()
|
|
1366
|
+
query = """
|
|
1367
|
+
SELECT tablename
|
|
1368
|
+
FROM pg_catalog.pg_tables
|
|
1369
|
+
WHERE schemaname != 'pg_catalog' AND schemaname != 'information_schema';
|
|
1370
|
+
"""
|
|
1371
|
+
self.cursor.execute(query)
|
|
1372
|
+
tables = [table[0] for table in self.cursor.fetchall()]
|
|
1373
|
+
return tables
|
|
1374
|
+
|
|
1375
|
+
def create_table(self, table_name, *column_tuples, include_id=False):
|
|
1376
|
+
self._check_connect_db()
|
|
1377
|
+
|
|
1378
|
+
if isinstance(column_tuples[0][0], (tuple, list)):
|
|
1379
|
+
column_tuples = column_tuples[0]
|
|
1380
|
+
|
|
1381
|
+
if include_id:
|
|
1382
|
+
id_column = ('id', 'SERIAL PRIMARY KEY')
|
|
1383
|
+
column_tuples = [id_column] + list(column_tuples)
|
|
1384
|
+
|
|
1385
|
+
columns_str = ', '.join([f'{col_name} {col_type}' for col_name, col_type in column_tuples])
|
|
1386
|
+
query = sql.SQL("CREATE TABLE {} ({})").format(
|
|
1387
|
+
sql.Identifier(table_name),
|
|
1388
|
+
sql.SQL(columns_str)
|
|
1389
|
+
)
|
|
1390
|
+
|
|
1391
|
+
self.cursor.execute(query)
|
|
1392
|
+
self.db_connection.commit()
|
|
1393
|
+
|
|
1394
|
+
def delete_table(self, table_name):
|
|
1395
|
+
self._check_connect_db()
|
|
1396
|
+
query = sql.SQL("DROP TABLE IF EXISTS {} CASCADE").format(sql.Identifier(table_name))
|
|
1397
|
+
self.cursor.execute(query)
|
|
1398
|
+
self.db_connection.commit()
|
|
1399
|
+
|
|
1400
|
+
def table_exists(self, table_name):
|
|
1401
|
+
self._check_connect_db()
|
|
1402
|
+
query = """
|
|
1403
|
+
SELECT EXISTS (
|
|
1404
|
+
SELECT 1
|
|
1405
|
+
FROM information_schema.tables
|
|
1406
|
+
WHERE table_schema = 'public' AND table_name = %s
|
|
1407
|
+
);
|
|
1408
|
+
"""
|
|
1409
|
+
self.cursor.execute(query, (table_name,))
|
|
1410
|
+
return self.cursor.fetchone()[0]
|
|
1411
|
+
|
|
1412
|
+
def rename_table(self, old_table_name, new_table_name):
|
|
1413
|
+
self._check_connect_db()
|
|
1414
|
+
query = sql.SQL("ALTER TABLE {} RENAME TO {}").format(
|
|
1415
|
+
sql.Identifier(old_table_name),
|
|
1416
|
+
sql.Identifier(new_table_name)
|
|
1417
|
+
)
|
|
1418
|
+
self.cursor.execute(query)
|
|
1419
|
+
self.db_connection.commit()
|
|
1420
|
+
|
|
1421
|
+
def reset_table(self, table_name, reset_auto_increment=True):
|
|
1422
|
+
"""
|
|
1423
|
+
This function deletes all rows from a table and optionally resets the auto-increment (SERIAL) sequence.
|
|
1424
|
+
|
|
1425
|
+
Parameters
|
|
1426
|
+
----------
|
|
1427
|
+
table_name : str
|
|
1428
|
+
Table name you want to erase from the currently connected db
|
|
1429
|
+
reset_auto_increment : bool, optional
|
|
1430
|
+
If TRUE, resets auto increment of id also (assumes column is named 'id')
|
|
1431
|
+
"""
|
|
1432
|
+
self._check_connect_db()
|
|
1433
|
+
|
|
1434
|
+
# Delete all rows from the table
|
|
1435
|
+
query = sql.SQL("DELETE FROM {}").format(sql.Identifier(table_name))
|
|
1436
|
+
self.cursor.execute(query)
|
|
1437
|
+
self.db_connection.commit()
|
|
1438
|
+
|
|
1439
|
+
# Reset auto-increment sequence if requested
|
|
1440
|
+
if reset_auto_increment:
|
|
1441
|
+
sequence_name = f"{table_name}_id_seq"
|
|
1442
|
+
reset_query = sql.SQL("ALTER SEQUENCE {} RESTART WITH 1").format(sql.Identifier(sequence_name))
|
|
1443
|
+
self.cursor.execute(reset_query)
|
|
1444
|
+
self.db_connection.commit()
|
|
1445
|
+
|
|
1446
|
+
def remove_rows_from_table(self, table_name, rows=1, order_column="id"):
|
|
1447
|
+
"""
|
|
1448
|
+
Removes a number of rows from a table by ordering with a specified column.
|
|
1449
|
+
|
|
1450
|
+
Parameters
|
|
1451
|
+
----------
|
|
1452
|
+
table_name : str
|
|
1453
|
+
Name of the table
|
|
1454
|
+
rows : int, optional
|
|
1455
|
+
Number of rows to remove (default is 1)
|
|
1456
|
+
order_column : str, optional
|
|
1457
|
+
Column by which to order the table for row removal (default is "id")
|
|
1458
|
+
"""
|
|
1459
|
+
self._check_connect_db()
|
|
1460
|
+
|
|
1461
|
+
query = sql.SQL(
|
|
1462
|
+
"DELETE FROM {} WHERE ctid IN (SELECT ctid FROM {} ORDER BY {} DESC LIMIT %s)"
|
|
1463
|
+
).format(
|
|
1464
|
+
sql.Identifier(table_name),
|
|
1465
|
+
sql.Identifier(table_name),
|
|
1466
|
+
sql.Identifier(order_column)
|
|
1467
|
+
)
|
|
1468
|
+
self.cursor.execute(query, (rows,))
|
|
1469
|
+
self.db_connection.commit()
|
|
1470
|
+
|
|
1471
|
+
|
|
1472
|
+
########################
|
|
1473
|
+
# Table Data Management: Data Checks and Queries
|
|
1474
|
+
def display_table_in_console(self, table_name, max_rows=20, min_column_width=15):
|
|
1475
|
+
"""
|
|
1476
|
+
Display a formatted table in the console with proper alignment and borders.
|
|
1477
|
+
|
|
1478
|
+
Parameters
|
|
1479
|
+
----------
|
|
1480
|
+
table_name : str
|
|
1481
|
+
Name of the table to display
|
|
1482
|
+
max_rows : int, optional
|
|
1483
|
+
Maximum number of rows to display (default is 20)
|
|
1484
|
+
min_column_width : int, optional
|
|
1485
|
+
Minimum width for each column (default is 15)
|
|
1486
|
+
|
|
1487
|
+
Returns
|
|
1488
|
+
-------
|
|
1489
|
+
None
|
|
1490
|
+
"""
|
|
1491
|
+
self._check_connect_db()
|
|
1492
|
+
select_query = f'SELECT * FROM "{table_name}"'
|
|
1493
|
+
self.cursor.execute(select_query)
|
|
1494
|
+
|
|
1495
|
+
columns = [desc[0] for desc in self.cursor.description]
|
|
1496
|
+
data = self.cursor.fetchmany(max_rows) if max_rows else self.cursor.fetchall()
|
|
1497
|
+
|
|
1498
|
+
col_widths = []
|
|
1499
|
+
for i, col in enumerate(columns):
|
|
1500
|
+
content_width = max(
|
|
1501
|
+
max(len(str(row[i])) for row in data) if data else 0,
|
|
1502
|
+
len(col)
|
|
1503
|
+
)
|
|
1504
|
+
col_widths.append(max(content_width + 2, min_column_width))
|
|
1505
|
+
|
|
1506
|
+
separator = '+' + '+'.join('-' * width for width in col_widths) + '+'
|
|
1507
|
+
|
|
1508
|
+
print(f"\nTable: {table_name}")
|
|
1509
|
+
print(separator)
|
|
1510
|
+
print('|' + '|'.join(
|
|
1511
|
+
f"{col:^{width}}" for col, width in zip(columns, col_widths)
|
|
1512
|
+
) + '|')
|
|
1513
|
+
print(separator)
|
|
1514
|
+
|
|
1515
|
+
for row in data:
|
|
1516
|
+
print('|' + '|'.join(
|
|
1517
|
+
f"{str(cell):^{width}}" for cell, width in zip(row, col_widths)
|
|
1518
|
+
) + '|')
|
|
1519
|
+
|
|
1520
|
+
print(separator)
|
|
1521
|
+
if max_rows and len(data) == max_rows:
|
|
1522
|
+
print(f"Note: Showing first {max_rows} rows only.")
|
|
1523
|
+
|
|
1524
|
+
def get_table_as_list(self, table_name, max_rows=None):
|
|
1525
|
+
self._check_connect_db()
|
|
1526
|
+
select_query = f'SELECT * FROM "{table_name}"'
|
|
1527
|
+
self.cursor.execute(select_query)
|
|
1528
|
+
|
|
1529
|
+
columns = [desc[0] for desc in self.cursor.description]
|
|
1530
|
+
data = self.cursor.fetchmany(max_rows) if max_rows else self.cursor.fetchall()
|
|
1531
|
+
|
|
1532
|
+
return [columns] + [list(row) for row in data]
|
|
1533
|
+
|
|
1534
|
+
def get_last_x_entries(self, table_name, x):
|
|
1535
|
+
"""
|
|
1536
|
+
This function assumes the table has an 'id' column to order by.
|
|
1537
|
+
|
|
1538
|
+
Parameters
|
|
1539
|
+
----------
|
|
1540
|
+
table_name : str
|
|
1541
|
+
Name of the table
|
|
1542
|
+
x : int
|
|
1543
|
+
Number of last entries to retrieve
|
|
1544
|
+
|
|
1545
|
+
Returns
|
|
1546
|
+
-------
|
|
1547
|
+
list
|
|
1548
|
+
List of rows including column headers
|
|
1549
|
+
"""
|
|
1550
|
+
self._check_connect_db()
|
|
1551
|
+
select_query = f'SELECT * FROM "{table_name}" ORDER BY id DESC LIMIT %s'
|
|
1552
|
+
self.cursor.execute(select_query, (x,))
|
|
1553
|
+
|
|
1554
|
+
columns = [desc[0] for desc in self.cursor.description]
|
|
1555
|
+
data = self.cursor.fetchall()
|
|
1556
|
+
return [columns] + [list(row) for row in data]
|
|
1252
1557
|
|
|
1558
|
+
def query_table_by_columns(self, table_name, *condition_tuples):
|
|
1559
|
+
"""
|
|
1560
|
+
Query the table by specified columns and conditions.
|
|
1253
1561
|
|
|
1562
|
+
Parameters
|
|
1563
|
+
----------
|
|
1564
|
+
table_name : str
|
|
1565
|
+
Name of the table to query
|
|
1566
|
+
condition_tuples : tuple(s) or list of tuples
|
|
1567
|
+
Conditions for the query, specified as tuples of column-value pairs
|
|
1254
1568
|
|
|
1569
|
+
Returns
|
|
1570
|
+
-------
|
|
1571
|
+
list
|
|
1572
|
+
A list of rows matching the query conditions
|
|
1573
|
+
"""
|
|
1574
|
+
self._check_connect_db()
|
|
1255
1575
|
|
|
1576
|
+
if isinstance(condition_tuples[0][0], (tuple, list)):
|
|
1577
|
+
conditions_list = [
|
|
1578
|
+
" AND ".join([f"{col} = %s" for col, _ in group]) for group in condition_tuples
|
|
1579
|
+
]
|
|
1580
|
+
values = [val for group in condition_tuples for _, val in group]
|
|
1581
|
+
else:
|
|
1582
|
+
conditions_list = [" AND ".join([f"{col} = %s" for col, _ in condition_tuples])]
|
|
1583
|
+
values = [val for _, val in condition_tuples]
|
|
1256
1584
|
|
|
1585
|
+
conditions_sql = " OR ".join(conditions_list)
|
|
1586
|
+
query = f'SELECT * FROM "{table_name}" WHERE {conditions_sql};'
|
|
1257
1587
|
|
|
1588
|
+
self.cursor.execute(query, values)
|
|
1589
|
+
return [list(row) for row in self.cursor.fetchall()]
|
|
1258
1590
|
|
|
1591
|
+
def query_by_month_day(self, table_name, date_column, date_string):
|
|
1592
|
+
"""
|
|
1593
|
+
Query records where the date matches the month and day.
|
|
1259
1594
|
|
|
1595
|
+
Parameters
|
|
1596
|
+
----------
|
|
1597
|
+
table_name : str
|
|
1598
|
+
Name of the table to query
|
|
1599
|
+
date_column : str
|
|
1600
|
+
The column name with date
|
|
1601
|
+
date_string : str
|
|
1602
|
+
Target date (e.g., '2024-01-15')
|
|
1603
|
+
|
|
1604
|
+
Returns
|
|
1605
|
+
-------
|
|
1606
|
+
list
|
|
1607
|
+
A list of rows matching the query conditions
|
|
1608
|
+
"""
|
|
1609
|
+
self._check_connect_db()
|
|
1610
|
+
|
|
1611
|
+
query = f'''
|
|
1612
|
+
SELECT *
|
|
1613
|
+
FROM "{table_name}"
|
|
1614
|
+
WHERE EXTRACT(MONTH FROM {date_column}) = EXTRACT(MONTH FROM %s::date)
|
|
1615
|
+
AND EXTRACT(DAY FROM {date_column}) = EXTRACT(DAY FROM %s::date)
|
|
1616
|
+
'''
|
|
1617
|
+
self.cursor.execute(query, (date_string, date_string))
|
|
1618
|
+
return [list(row) for row in self.cursor.fetchall()]
|
|
1619
|
+
|
|
1620
|
+
def get_columns_with_null_values(self, table_name, *lookup_condition_tuples):
|
|
1621
|
+
"""
|
|
1622
|
+
Get columns with null values in the specified row.
|
|
1623
|
+
|
|
1624
|
+
Parameters
|
|
1625
|
+
----------
|
|
1626
|
+
table_name : str
|
|
1627
|
+
Name of the table
|
|
1628
|
+
lookup_condition_tuples : tuple(s) or list of tuples
|
|
1629
|
+
Conditions to identify the row
|
|
1630
|
+
|
|
1631
|
+
Returns
|
|
1632
|
+
-------
|
|
1633
|
+
list of str
|
|
1634
|
+
Column names with NULL values in the row
|
|
1635
|
+
"""
|
|
1636
|
+
self._check_connect_db()
|
|
1637
|
+
|
|
1638
|
+
if isinstance(lookup_condition_tuples[0][0], (tuple, list)):
|
|
1639
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples[0]]
|
|
1640
|
+
values = [val for _, val in lookup_condition_tuples[0]]
|
|
1641
|
+
else:
|
|
1642
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples]
|
|
1643
|
+
values = [val for _, val in lookup_condition_tuples]
|
|
1644
|
+
|
|
1645
|
+
query = f'SELECT * FROM "{table_name}" WHERE {" AND ".join(conditions)} LIMIT 1'
|
|
1646
|
+
self.cursor.execute(query, values)
|
|
1647
|
+
row = self.cursor.fetchone()
|
|
1648
|
+
|
|
1649
|
+
if row is None:
|
|
1650
|
+
return []
|
|
1651
|
+
|
|
1652
|
+
columns = [desc[0] for desc in self.cursor.description]
|
|
1653
|
+
return [col for col, val in zip(columns, row) if val is None]
|
|
1654
|
+
|
|
1655
|
+
def is_value_null(self, table_name, to_check_column, *lookup_condition_tuples):
|
|
1656
|
+
"""
|
|
1657
|
+
Check if a value is NULL in the specified table and column.
|
|
1658
|
+
|
|
1659
|
+
Parameters
|
|
1660
|
+
----------
|
|
1661
|
+
table_name : str
|
|
1662
|
+
Table to check
|
|
1663
|
+
to_check_column : str
|
|
1664
|
+
Column to evaluate
|
|
1665
|
+
lookup_condition_tuples : tuple(s) or list of tuples
|
|
1666
|
+
Conditions to identify the row
|
|
1667
|
+
|
|
1668
|
+
Returns
|
|
1669
|
+
-------
|
|
1670
|
+
bool
|
|
1671
|
+
True if NULL, False otherwise
|
|
1672
|
+
"""
|
|
1673
|
+
result = self.get_single_value_from_table(table_name, to_check_column, *lookup_condition_tuples)
|
|
1674
|
+
return result is None
|
|
1675
|
+
|
|
1676
|
+
def check_if_data_exists(self, table_name, columns, values):
|
|
1677
|
+
"""
|
|
1678
|
+
Check if a specific row exists based on multiple column values.
|
|
1679
|
+
|
|
1680
|
+
Parameters
|
|
1681
|
+
----------
|
|
1682
|
+
table_name : str
|
|
1683
|
+
Table name
|
|
1684
|
+
columns : list of str
|
|
1685
|
+
Columns to match
|
|
1686
|
+
values : list
|
|
1687
|
+
Values to match against columns
|
|
1688
|
+
|
|
1689
|
+
Returns
|
|
1690
|
+
-------
|
|
1691
|
+
bool
|
|
1692
|
+
True if a match exists, False otherwise
|
|
1693
|
+
"""
|
|
1694
|
+
self._check_connect_db()
|
|
1695
|
+
|
|
1696
|
+
conditions = ' AND '.join([f"{col} = %s" for col in columns])
|
|
1697
|
+
query = f'SELECT COUNT(*) FROM "{table_name}" WHERE {conditions}'
|
|
1698
|
+
self.cursor.execute(query, values)
|
|
1699
|
+
return self.cursor.fetchone()[0] > 0
|
|
1700
|
+
|
|
1701
|
+
def check_if_value_exists_in_column(self, table_name, column, value_to_check):
|
|
1702
|
+
"""
|
|
1703
|
+
Check if a value exists in a specific column of a table.
|
|
1704
|
+
|
|
1705
|
+
Parameters
|
|
1706
|
+
----------
|
|
1707
|
+
table_name : str
|
|
1708
|
+
Table to search
|
|
1709
|
+
column : str
|
|
1710
|
+
Column name
|
|
1711
|
+
value_to_check : any
|
|
1712
|
+
Value to search for
|
|
1713
|
+
|
|
1714
|
+
Returns
|
|
1715
|
+
-------
|
|
1716
|
+
bool
|
|
1717
|
+
True if the value exists, False otherwise
|
|
1718
|
+
"""
|
|
1719
|
+
self._check_connect_db()
|
|
1720
|
+
|
|
1721
|
+
query = f'SELECT COUNT(*) FROM "{table_name}" WHERE {column} = %s'
|
|
1722
|
+
self.cursor.execute(query, (value_to_check,))
|
|
1723
|
+
return self.cursor.fetchone()[0] > 0
|
|
1724
|
+
|
|
1725
|
+
#######################
|
|
1726
|
+
# Table Data Management: Inserts and Updates
|
|
1727
|
+
def insert_data(self, table_name, columns, values, check_for_unique=False):
|
|
1728
|
+
"""
|
|
1729
|
+
Inserts a row into a PostgreSQL table. Optionally checks for uniqueness.
|
|
1730
|
+
|
|
1731
|
+
Parameters
|
|
1732
|
+
----------
|
|
1733
|
+
table_name : str
|
|
1734
|
+
columns : list of str
|
|
1735
|
+
values : list
|
|
1736
|
+
check_for_unique : bool
|
|
1737
|
+
|
|
1738
|
+
Returns
|
|
1739
|
+
-------
|
|
1740
|
+
bool
|
|
1741
|
+
"""
|
|
1742
|
+
add_data = True
|
|
1743
|
+
if check_for_unique:
|
|
1744
|
+
add_data = not self.check_if_data_exists(table_name, columns, values)
|
|
1745
|
+
|
|
1746
|
+
if add_data:
|
|
1747
|
+
self._check_connect_db()
|
|
1748
|
+
query = sql.SQL("INSERT INTO {} ({}) VALUES ({})").format(
|
|
1749
|
+
sql.Identifier(table_name),
|
|
1750
|
+
sql.SQL(', ').join(map(sql.Identifier, columns)),
|
|
1751
|
+
sql.SQL(', ').join(sql.Placeholder() * len(values))
|
|
1752
|
+
)
|
|
1753
|
+
self.cursor.execute(query, values)
|
|
1754
|
+
self.db_connection.commit()
|
|
1755
|
+
return True
|
|
1756
|
+
else:
|
|
1757
|
+
print("Data already exists in the table matching the information you are trying to add.")
|
|
1758
|
+
return False
|
|
1759
|
+
|
|
1760
|
+
def insert_data_as_table(self, table_name, table_columns, table_rows_list_of_list):
|
|
1761
|
+
"""
|
|
1762
|
+
Insert multiple rows into a PostgreSQL table.
|
|
1763
|
+
|
|
1764
|
+
Parameters
|
|
1765
|
+
----------
|
|
1766
|
+
table_name : str
|
|
1767
|
+
table_columns : list of str
|
|
1768
|
+
table_rows_list_of_list : list of list
|
|
1769
|
+
"""
|
|
1770
|
+
self._check_connect_db()
|
|
1771
|
+
|
|
1772
|
+
query = sql.SQL("INSERT INTO {} ({}) VALUES ({})").format(
|
|
1773
|
+
sql.Identifier(table_name),
|
|
1774
|
+
sql.SQL(', ').join(map(sql.Identifier, table_columns)),
|
|
1775
|
+
sql.SQL(', ').join(sql.Placeholder() * len(table_columns))
|
|
1776
|
+
)
|
|
1777
|
+
|
|
1778
|
+
try:
|
|
1779
|
+
for row_values in table_rows_list_of_list:
|
|
1780
|
+
self.cursor.execute(query, row_values)
|
|
1781
|
+
self.db_connection.commit()
|
|
1782
|
+
except Exception as e:
|
|
1783
|
+
self.db_connection.rollback()
|
|
1784
|
+
print(f"An error occurred: {e}")
|
|
1785
|
+
|
|
1786
|
+
def update_single_value_in_table(self, table_name, column_to_update, new_value, *lookup_condition_tuples):
|
|
1787
|
+
"""
|
|
1788
|
+
Update a single column's value in a specific row.
|
|
1789
|
+
|
|
1790
|
+
Parameters
|
|
1791
|
+
----------
|
|
1792
|
+
table_name : str
|
|
1793
|
+
column_to_update : str
|
|
1794
|
+
new_value : any
|
|
1795
|
+
lookup_condition_tuples : tuple(s)
|
|
1796
|
+
"""
|
|
1797
|
+
self._check_connect_db()
|
|
1798
|
+
|
|
1799
|
+
if isinstance(lookup_condition_tuples[0][0], (tuple, list)):
|
|
1800
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples[0]]
|
|
1801
|
+
values = [val for _, val in lookup_condition_tuples[0]]
|
|
1802
|
+
else:
|
|
1803
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples]
|
|
1804
|
+
values = [val for _, val in lookup_condition_tuples]
|
|
1805
|
+
|
|
1806
|
+
where_clause = " AND ".join(conditions)
|
|
1807
|
+
query = sql.SQL("UPDATE {} SET {} = %s WHERE " + where_clause).format(
|
|
1808
|
+
sql.Identifier(table_name),
|
|
1809
|
+
sql.Identifier(column_to_update)
|
|
1810
|
+
)
|
|
1811
|
+
self.cursor.execute(query, [new_value] + values)
|
|
1812
|
+
self.db_connection.commit()
|
|
1813
|
+
|
|
1814
|
+
def update_multiple_values_in_table(self, table_name, columns_to_update_list, values_to_update_list, *lookup_condition_tuples):
|
|
1815
|
+
"""
|
|
1816
|
+
Update multiple columns in a single row using WHERE conditions.
|
|
1817
|
+
|
|
1818
|
+
Parameters
|
|
1819
|
+
----------
|
|
1820
|
+
table_name : str
|
|
1821
|
+
columns_to_update_list : list of str
|
|
1822
|
+
values_to_update_list : list
|
|
1823
|
+
lookup_condition_tuples : tuple(s)
|
|
1824
|
+
"""
|
|
1825
|
+
self._check_connect_db()
|
|
1826
|
+
|
|
1827
|
+
if isinstance(lookup_condition_tuples[0][0], (tuple, list)):
|
|
1828
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples[0]]
|
|
1829
|
+
values = [val for _, val in lookup_condition_tuples[0]]
|
|
1830
|
+
else:
|
|
1831
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples]
|
|
1832
|
+
values = [val for _, val in lookup_condition_tuples]
|
|
1833
|
+
|
|
1834
|
+
set_clause = ', '.join([f"{col} = %s" for col in columns_to_update_list])
|
|
1835
|
+
where_clause = " AND ".join(conditions)
|
|
1836
|
+
|
|
1837
|
+
query = sql.SQL(f"UPDATE {{}} SET {set_clause} WHERE {where_clause}").format(
|
|
1838
|
+
sql.Identifier(table_name)
|
|
1839
|
+
)
|
|
1840
|
+
self.cursor.execute(query, values_to_update_list + values)
|
|
1841
|
+
self.db_connection.commit()
|
|
1842
|
+
|
|
1843
|
+
|
|
1844
|
+
#######################
|
|
1845
|
+
# Table Data Management: Columns
|
|
1846
|
+
def get_columns_in_table(self, table_name):
|
|
1847
|
+
"""
|
|
1848
|
+
Get the list of column names in the specified PostgreSQL table.
|
|
1849
|
+
|
|
1850
|
+
Parameters
|
|
1851
|
+
----------
|
|
1852
|
+
table_name : str
|
|
1853
|
+
|
|
1854
|
+
Returns
|
|
1855
|
+
-------
|
|
1856
|
+
list of str
|
|
1857
|
+
Column names
|
|
1858
|
+
"""
|
|
1859
|
+
self._check_connect_db()
|
|
1860
|
+
query = """
|
|
1861
|
+
SELECT column_name
|
|
1862
|
+
FROM information_schema.columns
|
|
1863
|
+
WHERE table_schema = 'public' AND table_name = %s;
|
|
1864
|
+
"""
|
|
1865
|
+
self.cursor.execute(query, (table_name,))
|
|
1866
|
+
return [row[0] for row in self.cursor.fetchall()]
|
|
1867
|
+
|
|
1868
|
+
def add_column_to_table(self, table_name, column_name, column_type, after_column=None):
|
|
1869
|
+
"""
|
|
1870
|
+
Add a column to the specified PostgreSQL table.
|
|
1871
|
+
|
|
1872
|
+
Parameters
|
|
1873
|
+
----------
|
|
1874
|
+
table_name : str
|
|
1875
|
+
column_name : str
|
|
1876
|
+
column_type : str
|
|
1877
|
+
after_column : str, optional (ignored in PostgreSQL)
|
|
1878
|
+
"""
|
|
1879
|
+
self._check_connect_db()
|
|
1880
|
+
|
|
1881
|
+
query = sql.SQL("ALTER TABLE {} ADD COLUMN {} {}").format(
|
|
1882
|
+
sql.Identifier(table_name),
|
|
1883
|
+
sql.Identifier(column_name),
|
|
1884
|
+
sql.SQL(column_type)
|
|
1885
|
+
)
|
|
1886
|
+
self.cursor.execute(query)
|
|
1887
|
+
self.db_connection.commit()
|
|
1888
|
+
|
|
1889
|
+
def rename_column(self, table_name, old_column_name, new_column_name):
|
|
1890
|
+
"""
|
|
1891
|
+
Rename a column in a PostgreSQL table.
|
|
1892
|
+
|
|
1893
|
+
Parameters
|
|
1894
|
+
----------
|
|
1895
|
+
table_name : str
|
|
1896
|
+
old_column_name : str
|
|
1897
|
+
new_column_name : str
|
|
1898
|
+
"""
|
|
1899
|
+
self._check_connect_db()
|
|
1900
|
+
query = sql.SQL("ALTER TABLE {} RENAME COLUMN {} TO {}").format(
|
|
1901
|
+
sql.Identifier(table_name),
|
|
1902
|
+
sql.Identifier(old_column_name),
|
|
1903
|
+
sql.Identifier(new_column_name)
|
|
1904
|
+
)
|
|
1905
|
+
self.cursor.execute(query)
|
|
1906
|
+
self.db_connection.commit()
|
|
1907
|
+
|
|
1908
|
+
def change_column_type(self, table_name, column_name, new_type):
|
|
1909
|
+
"""
|
|
1910
|
+
Change the data type of a column in PostgreSQL.
|
|
1911
|
+
|
|
1912
|
+
Parameters
|
|
1913
|
+
----------
|
|
1914
|
+
table_name : str
|
|
1915
|
+
column_name : str
|
|
1916
|
+
new_type : str
|
|
1917
|
+
"""
|
|
1918
|
+
self._check_connect_db()
|
|
1919
|
+
query = sql.SQL("ALTER TABLE {} ALTER COLUMN {} TYPE {}").format(
|
|
1920
|
+
sql.Identifier(table_name),
|
|
1921
|
+
sql.Identifier(column_name),
|
|
1922
|
+
sql.SQL(new_type)
|
|
1923
|
+
)
|
|
1924
|
+
self.cursor.execute(query)
|
|
1925
|
+
self.db_connection.commit()
|
|
1926
|
+
|
|
1927
|
+
def change_column_charset(self, table_name, column_name, new_charset, new_collation):
|
|
1928
|
+
"""
|
|
1929
|
+
Change the collation of a column in PostgreSQL (character sets are cluster-level).
|
|
1930
|
+
|
|
1931
|
+
Parameters
|
|
1932
|
+
----------
|
|
1933
|
+
table_name : str
|
|
1934
|
+
column_name : str
|
|
1935
|
+
new_charset : str (ignored – PostgreSQL uses UTF-8)
|
|
1936
|
+
new_collation : str (PostgreSQL collation name)
|
|
1937
|
+
"""
|
|
1938
|
+
self._check_connect_db()
|
|
1939
|
+
query = sql.SQL("ALTER TABLE {} ALTER COLUMN {} SET COLLATION {}").format(
|
|
1940
|
+
sql.Identifier(table_name),
|
|
1941
|
+
sql.Identifier(column_name),
|
|
1942
|
+
sql.Identifier(new_collation)
|
|
1943
|
+
)
|
|
1944
|
+
self.cursor.execute(query)
|
|
1945
|
+
self.db_connection.commit()
|
|
1946
|
+
|
|
1947
|
+
def delete_column(self, table_name, column_name):
|
|
1948
|
+
"""
|
|
1949
|
+
Delete a column from a PostgreSQL table.
|
|
1950
|
+
"""
|
|
1951
|
+
self._check_connect_db()
|
|
1952
|
+
query = sql.SQL("ALTER TABLE {} DROP COLUMN {}").format(
|
|
1953
|
+
sql.Identifier(table_name),
|
|
1954
|
+
sql.Identifier(column_name)
|
|
1955
|
+
)
|
|
1956
|
+
self.cursor.execute(query)
|
|
1957
|
+
self.db_connection.commit()
|
|
1958
|
+
|
|
1959
|
+
def get_column_data_as_list(self, table_name, column_name):
|
|
1960
|
+
"""
|
|
1961
|
+
Retrieve data from a column as a list.
|
|
1962
|
+
"""
|
|
1963
|
+
self._check_connect_db()
|
|
1964
|
+
query = sql.SQL("SELECT {} FROM {}").format(
|
|
1965
|
+
sql.Identifier(column_name),
|
|
1966
|
+
sql.Identifier(table_name)
|
|
1967
|
+
)
|
|
1968
|
+
self.cursor.execute(query)
|
|
1969
|
+
return [row[0] for row in self.cursor.fetchall()]
|
|
1970
|
+
|
|
1971
|
+
def initialize_values_in_column_to_provided_value(self, table, column_name, value):
|
|
1972
|
+
"""
|
|
1973
|
+
Initialize all values in a column to the provided value.
|
|
1974
|
+
"""
|
|
1975
|
+
self._check_connect_db()
|
|
1976
|
+
query = sql.SQL("UPDATE {} SET {} = %s").format(
|
|
1977
|
+
sql.Identifier(table),
|
|
1978
|
+
sql.Identifier(column_name)
|
|
1979
|
+
)
|
|
1980
|
+
self.cursor.execute(query, (value,))
|
|
1981
|
+
self.db_connection.commit()
|
|
1982
|
+
|
|
1983
|
+
def append_value_to_column(self, table_name, column_to_append, value_to_append, *lookup_condition_tuples):
|
|
1984
|
+
"""
|
|
1985
|
+
Append a value to a JSONB array in a PostgreSQL column.
|
|
1986
|
+
"""
|
|
1987
|
+
self._check_connect_db()
|
|
1988
|
+
|
|
1989
|
+
if isinstance(lookup_condition_tuples[0][0], (tuple, list)):
|
|
1990
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples[0]]
|
|
1991
|
+
values = [val for _, val in lookup_condition_tuples[0]]
|
|
1992
|
+
else:
|
|
1993
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples]
|
|
1994
|
+
values = [val for _, val in lookup_condition_tuples]
|
|
1995
|
+
|
|
1996
|
+
condition_sql = " AND ".join(conditions)
|
|
1997
|
+
query = f"""
|
|
1998
|
+
UPDATE {table_name}
|
|
1999
|
+
SET {column_to_append} = COALESCE({column_to_append}, '[]'::jsonb) || to_jsonb(%s::jsonb)
|
|
2000
|
+
WHERE {condition_sql}
|
|
2001
|
+
"""
|
|
2002
|
+
self.cursor.execute(query, [json.dumps(value_to_append)] + values)
|
|
2003
|
+
self.db_connection.commit()
|
|
2004
|
+
|
|
2005
|
+
def append_values_to_columns(self, table_name, columns_to_append_to, values_to_append, *lookup_condition_tuples):
|
|
2006
|
+
"""
|
|
2007
|
+
Append values to multiple JSONB columns in PostgreSQL.
|
|
2008
|
+
"""
|
|
2009
|
+
self._check_connect_db()
|
|
2010
|
+
|
|
2011
|
+
if isinstance(lookup_condition_tuples[0][0], (tuple, list)):
|
|
2012
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples[0]]
|
|
2013
|
+
values = [val for _, val in lookup_condition_tuples[0]]
|
|
2014
|
+
else:
|
|
2015
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples]
|
|
2016
|
+
values = [val for _, val in lookup_condition_tuples]
|
|
2017
|
+
|
|
2018
|
+
condition_sql = " AND ".join(conditions)
|
|
2019
|
+
update_parts = []
|
|
2020
|
+
value_placeholders = []
|
|
2021
|
+
|
|
2022
|
+
for col, val in zip(columns_to_append_to, values_to_append):
|
|
2023
|
+
update_parts.append(
|
|
2024
|
+
f"{col} = COALESCE({col}, '[]'::jsonb) || to_jsonb(%s::jsonb)"
|
|
2025
|
+
)
|
|
2026
|
+
value_placeholders.append(json.dumps(val))
|
|
2027
|
+
|
|
2028
|
+
query = f"""
|
|
2029
|
+
UPDATE {table_name}
|
|
2030
|
+
SET {', '.join(update_parts)}
|
|
2031
|
+
WHERE {condition_sql}
|
|
2032
|
+
"""
|
|
2033
|
+
self.cursor.execute(query, value_placeholders + values)
|
|
2034
|
+
self.db_connection.commit()
|
|
2035
|
+
|
|
2036
|
+
def append_values_to_columns_concat(self, table_name, columns_to_append_to, values_to_append, *lookup_condition_tuples):
|
|
2037
|
+
"""
|
|
2038
|
+
Append values to multiple columns using string concatenation.
|
|
2039
|
+
"""
|
|
2040
|
+
self._check_connect_db()
|
|
2041
|
+
|
|
2042
|
+
if isinstance(lookup_condition_tuples[0][0], (tuple, list)):
|
|
2043
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples[0]]
|
|
2044
|
+
values = [val for _, val in lookup_condition_tuples[0]]
|
|
2045
|
+
else:
|
|
2046
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples]
|
|
2047
|
+
values = [val for _, val in lookup_condition_tuples]
|
|
2048
|
+
|
|
2049
|
+
condition_sql = " AND ".join(conditions)
|
|
2050
|
+
|
|
2051
|
+
for col, val in zip(columns_to_append_to, values_to_append):
|
|
2052
|
+
query = f"""
|
|
2053
|
+
UPDATE {table_name}
|
|
2054
|
+
SET {col} = COALESCE({col}, '') || ', {json.dumps(val)}'
|
|
2055
|
+
WHERE {condition_sql}
|
|
2056
|
+
"""
|
|
2057
|
+
self.cursor.execute(query, values)
|
|
2058
|
+
self.db_connection.commit()
|
|
2059
|
+
|
|
2060
|
+
#######################
|
|
2061
|
+
# Table Data Management: Rows
|
|
2062
|
+
def get_total_rows(self, table_name):
|
|
2063
|
+
"""
|
|
2064
|
+
Get the total number of rows in the specified table.
|
|
2065
|
+
"""
|
|
2066
|
+
self._check_connect_db()
|
|
2067
|
+
query = sql.SQL("SELECT COUNT(*) FROM {}").format(sql.Identifier(table_name))
|
|
2068
|
+
self.cursor.execute(query)
|
|
2069
|
+
return self.cursor.fetchone()[0]
|
|
2070
|
+
|
|
2071
|
+
def delete_first_rows(self, table_name, x):
|
|
2072
|
+
"""
|
|
2073
|
+
Delete the first x rows from the specified table.
|
|
2074
|
+
NOTE: Requires a primary key or ordering column for deterministic behavior.
|
|
2075
|
+
"""
|
|
2076
|
+
self._check_connect_db()
|
|
2077
|
+
# Replace 'id' with your table's primary key if needed
|
|
2078
|
+
query = sql.SQL("""
|
|
2079
|
+
DELETE FROM {table}
|
|
2080
|
+
WHERE ctid IN (
|
|
2081
|
+
SELECT ctid FROM {table}
|
|
2082
|
+
ORDER BY ctid
|
|
2083
|
+
LIMIT %s
|
|
2084
|
+
)
|
|
2085
|
+
""").format(table=sql.Identifier(table_name))
|
|
2086
|
+
self.cursor.execute(query, (x,))
|
|
2087
|
+
self.db_connection.commit()
|
|
2088
|
+
|
|
2089
|
+
def get_single_value_from_table(self, table_name, column_to_retrieve, *lookup_condition_tuples):
|
|
2090
|
+
"""
|
|
2091
|
+
Retrieve a single value from the specified table.
|
|
2092
|
+
"""
|
|
2093
|
+
self._check_connect_db()
|
|
2094
|
+
|
|
2095
|
+
if isinstance(lookup_condition_tuples[0][0], (tuple, list)):
|
|
2096
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples[0]]
|
|
2097
|
+
values = [val for _, val in lookup_condition_tuples[0]]
|
|
2098
|
+
else:
|
|
2099
|
+
conditions = [f"{col} = %s" for col, _ in lookup_condition_tuples]
|
|
2100
|
+
values = [val for _, val in lookup_condition_tuples]
|
|
2101
|
+
|
|
2102
|
+
condition_sql = " AND ".join(conditions)
|
|
2103
|
+
query = sql.SQL("SELECT {} FROM {} WHERE {} LIMIT 1").format(
|
|
2104
|
+
sql.Identifier(column_to_retrieve),
|
|
2105
|
+
sql.Identifier(table_name),
|
|
2106
|
+
sql.SQL(condition_sql)
|
|
2107
|
+
)
|
|
2108
|
+
|
|
2109
|
+
self.cursor.execute(query, values)
|
|
2110
|
+
result = self.cursor.fetchone()
|
|
2111
|
+
|
|
2112
|
+
return result[0] if result and len(result) == 1 else result
|
|
2113
|
+
|
|
2114
|
+
def bulk_update_rows(self, table_name, columns_to_update, rows_to_update):
|
|
2115
|
+
"""
|
|
2116
|
+
Perform a bulk update of rows in the specified table.
|
|
2117
|
+
This method assumes the first column in columns_to_update is the unique key.
|
|
2118
|
+
"""
|
|
2119
|
+
self._check_connect_db()
|
|
2120
|
+
|
|
2121
|
+
key_column = columns_to_update[0]
|
|
2122
|
+
set_columns = columns_to_update[1:]
|
|
2123
|
+
|
|
2124
|
+
cases = {col: [] for col in set_columns}
|
|
2125
|
+
ids = []
|
|
2126
|
+
|
|
2127
|
+
for row in rows_to_update:
|
|
2128
|
+
row_dict = dict(zip(columns_to_update, row))
|
|
2129
|
+
key = row_dict[key_column]
|
|
2130
|
+
ids.append(key)
|
|
2131
|
+
for col in set_columns:
|
|
2132
|
+
cases[col].append((key, row_dict[col]))
|
|
2133
|
+
|
|
2134
|
+
set_clauses = []
|
|
2135
|
+
values = []
|
|
2136
|
+
|
|
2137
|
+
for col, case_list in cases.items():
|
|
2138
|
+
case_sql = f"{col} = CASE {key_column} " + \
|
|
2139
|
+
' '.join([f"WHEN %s THEN %s" for _ in case_list]) + " ELSE {col} END"
|
|
2140
|
+
set_clauses.append(case_sql)
|
|
2141
|
+
for pair in case_list:
|
|
2142
|
+
values.extend(pair)
|
|
2143
|
+
|
|
2144
|
+
query = f"""
|
|
2145
|
+
UPDATE {table_name}
|
|
2146
|
+
SET {', '.join(set_clauses)}
|
|
2147
|
+
WHERE {key_column} IN ({', '.join(['%s'] * len(ids))});
|
|
2148
|
+
"""
|
|
2149
|
+
values.extend(ids)
|
|
2150
|
+
self.cursor.execute(query, values)
|
|
2151
|
+
self.db_connection.commit()
|
|
2152
|
+
|
|
2153
|
+
|
|
2154
|
+
###################
|
|
2155
|
+
# General Query Execution
|
|
2156
|
+
def execute_query(self, query, params=None):
|
|
2157
|
+
"""
|
|
2158
|
+
Execute a SQL query with optional parameters.
|
|
2159
|
+
|
|
2160
|
+
Parameters
|
|
2161
|
+
----------
|
|
2162
|
+
query : str
|
|
2163
|
+
The SQL query to be executed
|
|
2164
|
+
params : tuple or list, optional
|
|
2165
|
+
Parameters to be passed to the query
|
|
2166
|
+
|
|
2167
|
+
Returns
|
|
2168
|
+
-------
|
|
2169
|
+
cursor
|
|
2170
|
+
For SELECT queries, returns cursor for fetching results
|
|
2171
|
+
None
|
|
2172
|
+
For other queries
|
|
2173
|
+
"""
|
|
2174
|
+
self._check_connect_db()
|
|
2175
|
+
|
|
2176
|
+
if params is None:
|
|
2177
|
+
self.cursor.execute(query)
|
|
2178
|
+
else:
|
|
2179
|
+
self.cursor.execute(query, params)
|
|
2180
|
+
|
|
2181
|
+
if query.strip().upper().startswith('SELECT'):
|
|
2182
|
+
return self.cursor
|
|
2183
|
+
|
|
2184
|
+
self.db_connection.commit()
|
|
2185
|
+
return None
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="lukhed_basic_utils",
|
|
5
|
-
version="1.6.
|
|
5
|
+
version="1.6.4",
|
|
6
6
|
description="A collection of basic utility functions",
|
|
7
7
|
long_description=open("README.md").read(),
|
|
8
8
|
long_description_content_type="text/markdown",
|
|
@@ -21,12 +21,13 @@ setup(
|
|
|
21
21
|
"requests>=2.32.3",
|
|
22
22
|
"beautifulsoup4>=4.12.3",
|
|
23
23
|
"fake-useragent>=2.0.3",
|
|
24
|
-
"tzdata>=2023.3",
|
|
24
|
+
"tzdata>=2023.3",
|
|
25
25
|
"PyGithub>= 2.5.0",
|
|
26
26
|
"matplotlib>=3.10.1",
|
|
27
27
|
"numpy>=2.2.4",
|
|
28
28
|
"pandas>=2.2.3",
|
|
29
29
|
"scipy>=1.15.2",
|
|
30
|
-
"mysql-connector-python>=8.0.0"
|
|
30
|
+
"mysql-connector-python>=8.0.0",
|
|
31
|
+
"psycopg2-binary>=2.9.10"
|
|
31
32
|
],
|
|
32
33
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibBarCharts.py
RENAMED
|
File without changes
|
{lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibBasics.py
RENAMED
|
File without changes
|
{lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibFormatting.py
RENAMED
|
File without changes
|
{lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibLineCharts.py
RENAMED
|
File without changes
|
{lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibPieCharts.py
RENAMED
|
File without changes
|
{lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibScatter.py
RENAMED
|
File without changes
|
{lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils/matplotlibSpecial.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{lukhed_basic_utils-1.6.2 → lukhed_basic_utils-1.6.4}/lukhed_basic_utils.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|