SQLPyHelper 0.1.3__tar.gz → 0.1.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.
- {sqlpyhelper-0.1.3 → sqlpyhelper-0.1.4}/PKG-INFO +41 -19
- {sqlpyhelper-0.1.3 → sqlpyhelper-0.1.4}/README.md +16 -8
- {sqlpyhelper-0.1.3 → sqlpyhelper-0.1.4}/SQLPyHelper.egg-info/PKG-INFO +41 -19
- {sqlpyhelper-0.1.3 → sqlpyhelper-0.1.4}/SQLPyHelper.egg-info/SOURCES.txt +5 -0
- sqlpyhelper-0.1.4/SQLPyHelper.egg-info/entry_points.txt +2 -0
- {sqlpyhelper-0.1.3 → sqlpyhelper-0.1.4}/SQLPyHelper.egg-info/requires.txt +4 -3
- sqlpyhelper-0.1.4/setup.py +63 -0
- sqlpyhelper-0.1.4/sqlpyhelper/__init__.py +9 -0
- sqlpyhelper-0.1.4/sqlpyhelper/automation_utils.py +145 -0
- sqlpyhelper-0.1.4/sqlpyhelper/cli.py +146 -0
- {sqlpyhelper-0.1.3 → sqlpyhelper-0.1.4}/sqlpyhelper/db_helper.py +114 -39
- sqlpyhelper-0.1.4/sqlpyhelper/py.typed +0 -0
- sqlpyhelper-0.1.4/test/test_automation.py +15 -0
- sqlpyhelper-0.1.3/setup.py +0 -38
- sqlpyhelper-0.1.3/sqlpyhelper/__init__.py +0 -2
- {sqlpyhelper-0.1.3 → sqlpyhelper-0.1.4}/LICENSE +0 -0
- {sqlpyhelper-0.1.3 → sqlpyhelper-0.1.4}/SQLPyHelper.egg-info/dependency_links.txt +0 -0
- {sqlpyhelper-0.1.3 → sqlpyhelper-0.1.4}/SQLPyHelper.egg-info/top_level.txt +0 -0
- {sqlpyhelper-0.1.3 → sqlpyhelper-0.1.4}/setup.cfg +0 -0
- {sqlpyhelper-0.1.3 → sqlpyhelper-0.1.4}/test/test_sqlpyhelper.py +0 -0
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SQLPyHelper
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A simple SQL database helper package for Python.
|
|
5
|
+
Home-page: https://github.com/adebayopeter/sqlpyhelper
|
|
5
6
|
Author: Adebayo Olaonipekun
|
|
6
7
|
Author-email: pekunmi@live.com
|
|
8
|
+
Project-URL: Source, https://github.com/adebayopeter/sqlpyhelper
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/adebayopeter/sqlpyhelper/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/adebayopeter/sqlpyhelper/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: database,sql,sqlite,postgresql,mysql,sqlserver,oracle,db,query,helper
|
|
7
12
|
Classifier: Programming Language :: Python :: 3
|
|
8
|
-
Classifier:
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
19
|
Classifier: Intended Audience :: Developers
|
|
10
20
|
Classifier: Topic :: Database :: Database Engines/Servers
|
|
11
21
|
Classifier: Operating System :: OS Independent
|
|
@@ -13,32 +23,44 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
13
23
|
Requires-Python: >=3.8
|
|
14
24
|
Description-Content-Type: text/markdown
|
|
15
25
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: psycopg2
|
|
17
|
-
Requires-Dist: mysql-connector-python
|
|
18
|
-
Requires-Dist: pyodbc
|
|
19
|
-
Requires-Dist: cx_Oracle
|
|
20
26
|
Requires-Dist: python-dotenv
|
|
21
|
-
|
|
22
|
-
Requires-Dist: mysql-connector-python; extra == "mysql"
|
|
27
|
+
Requires-Dist: click
|
|
23
28
|
Provides-Extra: postgres
|
|
24
29
|
Requires-Dist: psycopg2; extra == "postgres"
|
|
25
|
-
Provides-Extra:
|
|
26
|
-
Requires-Dist:
|
|
30
|
+
Provides-Extra: mysql
|
|
31
|
+
Requires-Dist: mysql-connector-python; extra == "mysql"
|
|
27
32
|
Provides-Extra: sqlserver
|
|
28
33
|
Requires-Dist: pyodbc; extra == "sqlserver"
|
|
29
|
-
Provides-Extra:
|
|
34
|
+
Provides-Extra: oracle
|
|
35
|
+
Requires-Dist: cx_Oracle; extra == "oracle"
|
|
36
|
+
Provides-Extra: all
|
|
37
|
+
Requires-Dist: psycopg2; extra == "all"
|
|
38
|
+
Requires-Dist: mysql-connector-python; extra == "all"
|
|
39
|
+
Requires-Dist: pyodbc; extra == "all"
|
|
40
|
+
Requires-Dist: cx_Oracle; extra == "all"
|
|
30
41
|
Dynamic: author
|
|
31
42
|
Dynamic: author-email
|
|
32
43
|
Dynamic: classifier
|
|
33
44
|
Dynamic: description
|
|
34
45
|
Dynamic: description-content-type
|
|
46
|
+
Dynamic: home-page
|
|
47
|
+
Dynamic: keywords
|
|
35
48
|
Dynamic: license-file
|
|
49
|
+
Dynamic: project-url
|
|
36
50
|
Dynamic: provides-extra
|
|
37
51
|
Dynamic: requires-dist
|
|
38
52
|
Dynamic: requires-python
|
|
39
53
|
Dynamic: summary
|
|
40
54
|
|
|
41
|
-
#
|
|
55
|
+
# SQLPyHelper
|
|
56
|
+
|
|
57
|
+
[](https://pypi.org/project/sqlpyhelper/)
|
|
58
|
+
[](https://pypi.org/project/sqlpyhelper/)
|
|
59
|
+
[](https://pypi.org/project/sqlpyhelper/)
|
|
60
|
+
[](https://github.com/adebayopeter/sqlpyhelper/blob/main/LICENSE)
|
|
61
|
+
[](https://github.com/adebayopeter/sqlpyhelper)
|
|
62
|
+
|
|
63
|
+
# 📌 SQLPyHelper v.0.1.4 🚀
|
|
42
64
|
|
|
43
65
|
A Python library for simplified database interactions across **SQLite, PostgreSQL, MySQL, SQL Server, and Oracle**. SQLPyHelper provides an intuitive API for handling queries, connection pooling, transactions, logging, and backups efficiently.
|
|
44
66
|
|
|
@@ -60,13 +82,13 @@ A Python library for simplified database interactions across **SQLite, PostgreSQ
|
|
|
60
82
|
---
|
|
61
83
|
|
|
62
84
|
## 🚀 Features in v0.1.3
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
- Unified connection pooling for multiple databases.
|
|
86
|
+
- Automatic reconnection for lost connections.
|
|
87
|
+
- Transaction support (BEGIN, ROLLBACK, COMMIT).
|
|
88
|
+
- Secure parameterized queries to prevent SQL injection.
|
|
89
|
+
- Bulk insertion & dynamic table creation.
|
|
90
|
+
- Logging & error handling for better debugging.
|
|
91
|
+
- CSV export & database backups.
|
|
70
92
|
|
|
71
93
|
---
|
|
72
94
|
## 📦 Installation
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# SQLPyHelper
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/sqlpyhelper/)
|
|
4
|
+
[](https://pypi.org/project/sqlpyhelper/)
|
|
5
|
+
[](https://pypi.org/project/sqlpyhelper/)
|
|
6
|
+
[](https://github.com/adebayopeter/sqlpyhelper/blob/main/LICENSE)
|
|
7
|
+
[](https://github.com/adebayopeter/sqlpyhelper)
|
|
8
|
+
|
|
9
|
+
# 📌 SQLPyHelper v.0.1.4 🚀
|
|
2
10
|
|
|
3
11
|
A Python library for simplified database interactions across **SQLite, PostgreSQL, MySQL, SQL Server, and Oracle**. SQLPyHelper provides an intuitive API for handling queries, connection pooling, transactions, logging, and backups efficiently.
|
|
4
12
|
|
|
@@ -20,13 +28,13 @@ A Python library for simplified database interactions across **SQLite, PostgreSQ
|
|
|
20
28
|
---
|
|
21
29
|
|
|
22
30
|
## 🚀 Features in v0.1.3
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
- Unified connection pooling for multiple databases.
|
|
32
|
+
- Automatic reconnection for lost connections.
|
|
33
|
+
- Transaction support (BEGIN, ROLLBACK, COMMIT).
|
|
34
|
+
- Secure parameterized queries to prevent SQL injection.
|
|
35
|
+
- Bulk insertion & dynamic table creation.
|
|
36
|
+
- Logging & error handling for better debugging.
|
|
37
|
+
- CSV export & database backups.
|
|
30
38
|
|
|
31
39
|
---
|
|
32
40
|
## 📦 Installation
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SQLPyHelper
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A simple SQL database helper package for Python.
|
|
5
|
+
Home-page: https://github.com/adebayopeter/sqlpyhelper
|
|
5
6
|
Author: Adebayo Olaonipekun
|
|
6
7
|
Author-email: pekunmi@live.com
|
|
8
|
+
Project-URL: Source, https://github.com/adebayopeter/sqlpyhelper
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/adebayopeter/sqlpyhelper/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/adebayopeter/sqlpyhelper/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: database,sql,sqlite,postgresql,mysql,sqlserver,oracle,db,query,helper
|
|
7
12
|
Classifier: Programming Language :: Python :: 3
|
|
8
|
-
Classifier:
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
19
|
Classifier: Intended Audience :: Developers
|
|
10
20
|
Classifier: Topic :: Database :: Database Engines/Servers
|
|
11
21
|
Classifier: Operating System :: OS Independent
|
|
@@ -13,32 +23,44 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
13
23
|
Requires-Python: >=3.8
|
|
14
24
|
Description-Content-Type: text/markdown
|
|
15
25
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: psycopg2
|
|
17
|
-
Requires-Dist: mysql-connector-python
|
|
18
|
-
Requires-Dist: pyodbc
|
|
19
|
-
Requires-Dist: cx_Oracle
|
|
20
26
|
Requires-Dist: python-dotenv
|
|
21
|
-
|
|
22
|
-
Requires-Dist: mysql-connector-python; extra == "mysql"
|
|
27
|
+
Requires-Dist: click
|
|
23
28
|
Provides-Extra: postgres
|
|
24
29
|
Requires-Dist: psycopg2; extra == "postgres"
|
|
25
|
-
Provides-Extra:
|
|
26
|
-
Requires-Dist:
|
|
30
|
+
Provides-Extra: mysql
|
|
31
|
+
Requires-Dist: mysql-connector-python; extra == "mysql"
|
|
27
32
|
Provides-Extra: sqlserver
|
|
28
33
|
Requires-Dist: pyodbc; extra == "sqlserver"
|
|
29
|
-
Provides-Extra:
|
|
34
|
+
Provides-Extra: oracle
|
|
35
|
+
Requires-Dist: cx_Oracle; extra == "oracle"
|
|
36
|
+
Provides-Extra: all
|
|
37
|
+
Requires-Dist: psycopg2; extra == "all"
|
|
38
|
+
Requires-Dist: mysql-connector-python; extra == "all"
|
|
39
|
+
Requires-Dist: pyodbc; extra == "all"
|
|
40
|
+
Requires-Dist: cx_Oracle; extra == "all"
|
|
30
41
|
Dynamic: author
|
|
31
42
|
Dynamic: author-email
|
|
32
43
|
Dynamic: classifier
|
|
33
44
|
Dynamic: description
|
|
34
45
|
Dynamic: description-content-type
|
|
46
|
+
Dynamic: home-page
|
|
47
|
+
Dynamic: keywords
|
|
35
48
|
Dynamic: license-file
|
|
49
|
+
Dynamic: project-url
|
|
36
50
|
Dynamic: provides-extra
|
|
37
51
|
Dynamic: requires-dist
|
|
38
52
|
Dynamic: requires-python
|
|
39
53
|
Dynamic: summary
|
|
40
54
|
|
|
41
|
-
#
|
|
55
|
+
# SQLPyHelper
|
|
56
|
+
|
|
57
|
+
[](https://pypi.org/project/sqlpyhelper/)
|
|
58
|
+
[](https://pypi.org/project/sqlpyhelper/)
|
|
59
|
+
[](https://pypi.org/project/sqlpyhelper/)
|
|
60
|
+
[](https://github.com/adebayopeter/sqlpyhelper/blob/main/LICENSE)
|
|
61
|
+
[](https://github.com/adebayopeter/sqlpyhelper)
|
|
62
|
+
|
|
63
|
+
# 📌 SQLPyHelper v.0.1.4 🚀
|
|
42
64
|
|
|
43
65
|
A Python library for simplified database interactions across **SQLite, PostgreSQL, MySQL, SQL Server, and Oracle**. SQLPyHelper provides an intuitive API for handling queries, connection pooling, transactions, logging, and backups efficiently.
|
|
44
66
|
|
|
@@ -60,13 +82,13 @@ A Python library for simplified database interactions across **SQLite, PostgreSQ
|
|
|
60
82
|
---
|
|
61
83
|
|
|
62
84
|
## 🚀 Features in v0.1.3
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
- Unified connection pooling for multiple databases.
|
|
86
|
+
- Automatic reconnection for lost connections.
|
|
87
|
+
- Transaction support (BEGIN, ROLLBACK, COMMIT).
|
|
88
|
+
- Secure parameterized queries to prevent SQL injection.
|
|
89
|
+
- Bulk insertion & dynamic table creation.
|
|
90
|
+
- Logging & error handling for better debugging.
|
|
91
|
+
- CSV export & database backups.
|
|
70
92
|
|
|
71
93
|
---
|
|
72
94
|
## 📦 Installation
|
|
@@ -4,8 +4,13 @@ setup.py
|
|
|
4
4
|
SQLPyHelper.egg-info/PKG-INFO
|
|
5
5
|
SQLPyHelper.egg-info/SOURCES.txt
|
|
6
6
|
SQLPyHelper.egg-info/dependency_links.txt
|
|
7
|
+
SQLPyHelper.egg-info/entry_points.txt
|
|
7
8
|
SQLPyHelper.egg-info/requires.txt
|
|
8
9
|
SQLPyHelper.egg-info/top_level.txt
|
|
9
10
|
sqlpyhelper/__init__.py
|
|
11
|
+
sqlpyhelper/automation_utils.py
|
|
12
|
+
sqlpyhelper/cli.py
|
|
10
13
|
sqlpyhelper/db_helper.py
|
|
14
|
+
sqlpyhelper/py.typed
|
|
15
|
+
test/test_automation.py
|
|
11
16
|
test/test_sqlpyhelper.py
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
python-dotenv
|
|
2
|
+
click
|
|
3
|
+
|
|
4
|
+
[all]
|
|
1
5
|
psycopg2
|
|
2
6
|
mysql-connector-python
|
|
3
7
|
pyodbc
|
|
4
8
|
cx_Oracle
|
|
5
|
-
python-dotenv
|
|
6
9
|
|
|
7
10
|
[mysql]
|
|
8
11
|
mysql-connector-python
|
|
@@ -13,7 +16,5 @@ cx_Oracle
|
|
|
13
16
|
[postgres]
|
|
14
17
|
psycopg2
|
|
15
18
|
|
|
16
|
-
[sqlite]
|
|
17
|
-
|
|
18
19
|
[sqlserver]
|
|
19
20
|
pyodbc
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as f:
|
|
4
|
+
long_description = f.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name='SQLPyHelper',
|
|
8
|
+
version='0.1.4',
|
|
9
|
+
description='A simple SQL database helper package for Python.',
|
|
10
|
+
long_description=long_description,
|
|
11
|
+
long_description_content_type="text/markdown",
|
|
12
|
+
author='Adebayo Olaonipekun',
|
|
13
|
+
author_email='pekunmi@live.com',
|
|
14
|
+
url='https://github.com/adebayopeter/sqlpyhelper',
|
|
15
|
+
packages=find_packages(),
|
|
16
|
+
package_data={
|
|
17
|
+
"sqlpyhelper": ["py.typed"],
|
|
18
|
+
},
|
|
19
|
+
python_requires=">=3.8",
|
|
20
|
+
install_requires=[
|
|
21
|
+
'python-dotenv',
|
|
22
|
+
'click'
|
|
23
|
+
],
|
|
24
|
+
extras_require={
|
|
25
|
+
"postgres": ["psycopg2"],
|
|
26
|
+
"mysql": ["mysql-connector-python"],
|
|
27
|
+
"sqlserver": ["pyodbc"],
|
|
28
|
+
"oracle": ["cx_Oracle"],
|
|
29
|
+
"all": [
|
|
30
|
+
"psycopg2",
|
|
31
|
+
"mysql-connector-python",
|
|
32
|
+
"pyodbc",
|
|
33
|
+
"cx_Oracle",
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
keywords=[
|
|
37
|
+
"database", "sql", "sqlite", "postgresql", "mysql",
|
|
38
|
+
"sqlserver", "oracle", "db", "query", "helper",
|
|
39
|
+
],
|
|
40
|
+
project_urls={
|
|
41
|
+
"Source": "https://github.com/adebayopeter/sqlpyhelper",
|
|
42
|
+
"Bug Tracker": "https://github.com/adebayopeter/sqlpyhelper/issues",
|
|
43
|
+
"Changelog": "https://github.com/adebayopeter/sqlpyhelper/blob/main/CHANGELOG.md",
|
|
44
|
+
},
|
|
45
|
+
classifiers=[
|
|
46
|
+
"Programming Language :: Python :: 3",
|
|
47
|
+
"Programming Language :: Python :: 3.8",
|
|
48
|
+
"Programming Language :: Python :: 3.9",
|
|
49
|
+
"Programming Language :: Python :: 3.10",
|
|
50
|
+
"Programming Language :: Python :: 3.11",
|
|
51
|
+
"Programming Language :: Python :: 3.12",
|
|
52
|
+
"Development Status :: 4 - Beta",
|
|
53
|
+
"Intended Audience :: Developers",
|
|
54
|
+
"Topic :: Database :: Database Engines/Servers",
|
|
55
|
+
"Operating System :: OS Independent",
|
|
56
|
+
"License :: OSI Approved :: MIT License",
|
|
57
|
+
],
|
|
58
|
+
entry_points={
|
|
59
|
+
'console_scripts': [
|
|
60
|
+
'sqlpyhelper=sqlpyhelper.cli:cli',
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from sqlpyhelper.db_helper import SQLPyHelper
|
|
3
|
+
import subprocess
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AutomationUtils:
|
|
10
|
+
def __init__(self, db=None, **db_kwargs):
|
|
11
|
+
"""
|
|
12
|
+
Optionally accepts db instance or connection parameters like:
|
|
13
|
+
db_type, host, user, password, database, port, driver.
|
|
14
|
+
"""
|
|
15
|
+
self.db = db or SQLPyHelper(**db_kwargs)
|
|
16
|
+
|
|
17
|
+
def backup_database(self, target="local", tag="autobackup"):
|
|
18
|
+
"""
|
|
19
|
+
Backs up the active PostgreSQL database using pg_dump.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
target (str): Backup destination ("local" only for now).
|
|
23
|
+
tag (str): Custom tag for backup file naming.
|
|
24
|
+
"""
|
|
25
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M")
|
|
26
|
+
filename = f"{tag}_{timestamp}.sql"
|
|
27
|
+
backup_dir = "backups"
|
|
28
|
+
os.makedirs(backup_dir, exist_ok=True)
|
|
29
|
+
filepath = os.path.join(backup_dir, filename)
|
|
30
|
+
|
|
31
|
+
db_name = self.db.database
|
|
32
|
+
user = self.db.user
|
|
33
|
+
host = self.db.host or "localhost"
|
|
34
|
+
port = str(self.db.port or "5432")
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
if self.db.db_type == "sqlite":
|
|
38
|
+
filename2 = f"{tag}_{timestamp}.db"
|
|
39
|
+
sqlite_filepath = os.path.join(backup_dir, filename2)
|
|
40
|
+
shutil.copy2(self.db.database, sqlite_filepath)
|
|
41
|
+
else:
|
|
42
|
+
print(f"📦 Backing up database to {filepath}")
|
|
43
|
+
subprocess.run(
|
|
44
|
+
[
|
|
45
|
+
"pg_dump",
|
|
46
|
+
"-h", host,
|
|
47
|
+
"-p", port,
|
|
48
|
+
"-U", user,
|
|
49
|
+
db_name,
|
|
50
|
+
"-F", "c",
|
|
51
|
+
"-f", filepath,
|
|
52
|
+
],
|
|
53
|
+
check=True,
|
|
54
|
+
shell=False,
|
|
55
|
+
)
|
|
56
|
+
except subprocess.CalledProcessError as e:
|
|
57
|
+
print("❌ Backup failed:", e)
|
|
58
|
+
|
|
59
|
+
def load_data_from_csv(self, file_path, table_name, if_exists="append"):
|
|
60
|
+
"""
|
|
61
|
+
Loads a CSV file into the specified database table.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
file_path (str): Path to the CSV file.
|
|
65
|
+
table_name (str): Destination table name in the database.
|
|
66
|
+
if_exists (str): 'append' or 'replace'. Default is 'append'.
|
|
67
|
+
"""
|
|
68
|
+
df = pd.read_csv(file_path)
|
|
69
|
+
|
|
70
|
+
if if_exists == "replace":
|
|
71
|
+
self.db.execute_query(f"DROP TABLE IF EXISTS {table_name}")
|
|
72
|
+
|
|
73
|
+
for _, row in df.iterrows():
|
|
74
|
+
self.db.insert_dynamic(table_name, row.to_dict())
|
|
75
|
+
|
|
76
|
+
def detect_missing_periods(self, table, entity_column, date_column):
|
|
77
|
+
"""
|
|
78
|
+
Flags rows where recurring time periods (e.g. monthly) are missing per entity.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
table (str): Table name to query.
|
|
82
|
+
entity_column (str): Column representing entity ID.
|
|
83
|
+
date_column (str): Column representing timestamp/date.
|
|
84
|
+
"""
|
|
85
|
+
if self.db.db_type == 'sqlite':
|
|
86
|
+
month_expr = f"strftime('%Y-%m', {date_column})"
|
|
87
|
+
else:
|
|
88
|
+
month_expr = f"DATE_TRUNC('month', {date_column})"
|
|
89
|
+
query = f"""
|
|
90
|
+
SELECT {entity_column}, COUNT(DISTINCT {month_expr}) AS recorded_months
|
|
91
|
+
FROM {table}
|
|
92
|
+
GROUP BY {entity_column}
|
|
93
|
+
HAVING COUNT(DISTINCT {month_expr}) < 12
|
|
94
|
+
"""
|
|
95
|
+
self.db.execute_query(query)
|
|
96
|
+
return self.db.fetch_all()
|
|
97
|
+
|
|
98
|
+
def aggregate_column(self, table, value_column, group_column=None, time_column=None):
|
|
99
|
+
"""
|
|
100
|
+
Computes sum of any value column grouped by entity or month.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
table (str): Table name.
|
|
104
|
+
value_column (str): Numeric column to aggregate.
|
|
105
|
+
group_column (str, optional): Entity or category to group by.
|
|
106
|
+
time_column (str, optional): Timestamp to extract month grouping.
|
|
107
|
+
"""
|
|
108
|
+
if self.db.db_type == 'sqlite':
|
|
109
|
+
month_expr = f"strftime('%Y-%m', {time_column})"
|
|
110
|
+
else:
|
|
111
|
+
month_expr = f"DATE_TRUNC('month', {time_column})"
|
|
112
|
+
|
|
113
|
+
if group_column and time_column:
|
|
114
|
+
query = f"""
|
|
115
|
+
SELECT {group_column}, {month_expr} AS month, SUM({value_column}) AS total
|
|
116
|
+
FROM {table}
|
|
117
|
+
GROUP BY {group_column}, month
|
|
118
|
+
ORDER BY month
|
|
119
|
+
"""
|
|
120
|
+
else:
|
|
121
|
+
query = f"SELECT SUM({value_column}) FROM {table}"
|
|
122
|
+
|
|
123
|
+
self.db.execute_query(query)
|
|
124
|
+
return self.db.fetch_all()
|
|
125
|
+
|
|
126
|
+
def detect_outliers(self, table, numeric_column, threshold=2):
|
|
127
|
+
"""
|
|
128
|
+
Detects statistical outliers based on deviation from mean.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
table (str): Table name.
|
|
132
|
+
numeric_column (str): Column to analyze.
|
|
133
|
+
threshold (int): Number of standard deviations from mean to flag as outlier.
|
|
134
|
+
"""
|
|
135
|
+
query = f"""
|
|
136
|
+
SELECT *, {numeric_column}
|
|
137
|
+
AS value FROM {table}
|
|
138
|
+
"""
|
|
139
|
+
self.db.execute_query(query)
|
|
140
|
+
data = pd.DataFrame(self.db.fetch_all(), columns=[desc[0] for desc in self.db.cursor.description])
|
|
141
|
+
|
|
142
|
+
mean_val = data["value"].mean()
|
|
143
|
+
std_val = data["value"].std()
|
|
144
|
+
outliers = data[abs(data["value"] - mean_val) > threshold * std_val]
|
|
145
|
+
return outliers.values.tolist()
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from sqlpyhelper.db_helper import SQLPyHelper
|
|
3
|
+
from sqlpyhelper.automation_utils import AutomationUtils
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.group()
|
|
7
|
+
def cli():
|
|
8
|
+
"""SQLPyHelper Command Line Interface"""
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@cli.command()
|
|
13
|
+
@click.option('--db_type', help='Type of database (e.g., sqlite, postgres, mysql)')
|
|
14
|
+
@click.option('--host', help='Database host')
|
|
15
|
+
@click.option('--user', help='Username')
|
|
16
|
+
@click.option('--password', help='Password')
|
|
17
|
+
@click.option('--database', help='Database name or file')
|
|
18
|
+
@click.option('--query', required=True, help='SQL query to run')
|
|
19
|
+
def run_query(db_type, host, user, password, database, query):
|
|
20
|
+
"""Run a single SQL query and print results"""
|
|
21
|
+
db = SQLPyHelper(db_type=db_type, host=host, user=user, password=password, database=database)
|
|
22
|
+
results = db.execute_query(query)
|
|
23
|
+
for row in results:
|
|
24
|
+
click.echo(row)
|
|
25
|
+
db.close()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@cli.command()
|
|
29
|
+
@click.option('--db_type', required=True)
|
|
30
|
+
@click.option('--host')
|
|
31
|
+
@click.option('--user')
|
|
32
|
+
@click.option('--password')
|
|
33
|
+
@click.option('--database', required=True)
|
|
34
|
+
def interactive_shell(db_type, host, user, password, database):
|
|
35
|
+
"""Launch an interactive SQL shell"""
|
|
36
|
+
db = SQLPyHelper(db_type=db_type, host=host, user=user, password=password, database=database)
|
|
37
|
+
click.echo("Interactive shell started. Type your SQL query or 'exit'")
|
|
38
|
+
while True:
|
|
39
|
+
query = input("sqlpy> ")
|
|
40
|
+
if query.lower() in ("exit", "quit"):
|
|
41
|
+
break
|
|
42
|
+
try:
|
|
43
|
+
db.execute_query(query)
|
|
44
|
+
results = db.fetch_all()
|
|
45
|
+
for row in results:
|
|
46
|
+
click.echo(row)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
click.echo(f"Error: {e}")
|
|
49
|
+
db.close()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@cli.command()
|
|
53
|
+
@click.option('--target', default="local", help="Backup destination")
|
|
54
|
+
@click.option('--tag', default="autobackup", help="Tag for backup file naming")
|
|
55
|
+
@click.option('--db-type')
|
|
56
|
+
@click.option('--host')
|
|
57
|
+
@click.option('--user')
|
|
58
|
+
@click.option('--password')
|
|
59
|
+
@click.option('--database')
|
|
60
|
+
@click.option('--port')
|
|
61
|
+
def backup(target, tag, db_type, host, user, password, database, port):
|
|
62
|
+
"""Create a timestamped backup of the connected database."""
|
|
63
|
+
utils = AutomationUtils(
|
|
64
|
+
db_type=db_type,
|
|
65
|
+
host=host,
|
|
66
|
+
user=user,
|
|
67
|
+
password=password,
|
|
68
|
+
database=database,
|
|
69
|
+
port=port
|
|
70
|
+
)
|
|
71
|
+
utils.backup_database(target=target, tag=tag)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@cli.command()
|
|
75
|
+
@click.option('--file', required=True, help="Path to CSV file")
|
|
76
|
+
@click.option('--table', required=True, help="Destination table")
|
|
77
|
+
@click.option('--if-exists', default="append", type=click.Choice(["append", "replace"]), help="What to do if table exists")
|
|
78
|
+
@click.option('--db-type')
|
|
79
|
+
@click.option('--host')
|
|
80
|
+
@click.option('--user')
|
|
81
|
+
@click.option('--password')
|
|
82
|
+
@click.option('--database')
|
|
83
|
+
@click.option('--port')
|
|
84
|
+
def load_data(file, table, if_exists, db_type, host, user, password, database, port):
|
|
85
|
+
"""Load data from CSV into database table."""
|
|
86
|
+
utils = AutomationUtils(db_type=db_type, host=host, user=user, password=password, database=database, port=port)
|
|
87
|
+
utils.load_data_from_csv(file, table, if_exists=if_exists)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@cli.command()
|
|
91
|
+
@click.option('--table', required=True)
|
|
92
|
+
@click.option('--entity-column', required=True)
|
|
93
|
+
@click.option('--date-column', required=True)
|
|
94
|
+
@click.option('--db-type')
|
|
95
|
+
@click.option('--host')
|
|
96
|
+
@click.option('--user')
|
|
97
|
+
@click.option('--password')
|
|
98
|
+
@click.option('--database')
|
|
99
|
+
@click.option('--port')
|
|
100
|
+
def detect_missing_periods(table, entity_column, date_column, db_type, host, user, password, database, port):
|
|
101
|
+
"""Flag entities with fewer than 12 months of activity."""
|
|
102
|
+
utils = AutomationUtils(db_type=db_type, host=host, user=user, password=password, database=database, port=port)
|
|
103
|
+
results = utils.detect_missing_periods(table, entity_column, date_column)
|
|
104
|
+
for row in results:
|
|
105
|
+
click.echo(row)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@cli.command()
|
|
109
|
+
@click.option('--table', required=True)
|
|
110
|
+
@click.option('--value-column', required=True)
|
|
111
|
+
@click.option('--group-column')
|
|
112
|
+
@click.option('--time-column')
|
|
113
|
+
@click.option('--db-type')
|
|
114
|
+
@click.option('--host')
|
|
115
|
+
@click.option('--user')
|
|
116
|
+
@click.option('--password')
|
|
117
|
+
@click.option('--database')
|
|
118
|
+
@click.option('--port')
|
|
119
|
+
def aggregate(table, value_column, group_column, time_column, db_type, host, user, password, database, port):
|
|
120
|
+
"""Aggregate numeric column optionally grouped by entity and time."""
|
|
121
|
+
utils = AutomationUtils(db_type=db_type, host=host, user=user, password=password, database=database, port=port)
|
|
122
|
+
results = utils.aggregate_column(table, value_column, group_column, time_column)
|
|
123
|
+
for row in results:
|
|
124
|
+
click.echo(row)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@cli.command()
|
|
128
|
+
@click.option('--table', required=True)
|
|
129
|
+
@click.option('--numeric-column', required=True)
|
|
130
|
+
@click.option('--threshold', default=2, type=int)
|
|
131
|
+
@click.option('--db-type')
|
|
132
|
+
@click.option('--host')
|
|
133
|
+
@click.option('--user')
|
|
134
|
+
@click.option('--password')
|
|
135
|
+
@click.option('--database')
|
|
136
|
+
@click.option('--port')
|
|
137
|
+
def detect_outliers(table, numeric_column, threshold, db_type, host, user, password, database, port):
|
|
138
|
+
"""Flag rows where values deviate statistically from average."""
|
|
139
|
+
utils = AutomationUtils(db_type=db_type, host=host, user=user, password=password, database=database, port=port)
|
|
140
|
+
results = utils.detect_outliers(table, numeric_column, threshold)
|
|
141
|
+
for row in results:
|
|
142
|
+
click.echo(row)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
cli()
|
|
@@ -1,27 +1,70 @@
|
|
|
1
1
|
import csv
|
|
2
2
|
from dotenv import load_dotenv
|
|
3
3
|
import os
|
|
4
|
+
import re
|
|
4
5
|
|
|
5
6
|
load_dotenv() # Load environment variables from .env file
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
def
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
def _validate_identifier(name: str) -> str:
|
|
10
|
+
"""
|
|
11
|
+
Validate a SQL identifier (table or column name).
|
|
12
|
+
Allows only alphanumeric characters and underscores.
|
|
13
|
+
Raises ValueError for anything else, preventing SQL injection via identifiers.
|
|
14
|
+
"""
|
|
15
|
+
if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', name):
|
|
16
|
+
raise ValueError(
|
|
17
|
+
f"Invalid SQL identifier: {name!r}. "
|
|
18
|
+
"Only letters, digits, and underscores are allowed."
|
|
19
|
+
)
|
|
20
|
+
return name
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SQLPyHelperError(Exception):
|
|
24
|
+
"""Base exception for SQLPyHelper errors."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ConnectionError(SQLPyHelperError):
|
|
28
|
+
"""Raised when a database connection fails."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class QueryError(SQLPyHelperError):
|
|
32
|
+
"""Raised when a query fails to execute."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BackupError(SQLPyHelperError):
|
|
36
|
+
"""Raised when a backup operation fails."""
|
|
12
37
|
|
|
13
38
|
|
|
14
39
|
class SQLPyHelper:
|
|
15
|
-
def __init__(self
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
self.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
def __init__(self, db_type=None, host=None, user=None, password=None,
|
|
41
|
+
database=None, driver=None, port=None, oracle_sid=None):
|
|
42
|
+
|
|
43
|
+
# Store original params so reconnect() can replay them
|
|
44
|
+
self._init_kwargs = {
|
|
45
|
+
"db_type": db_type,
|
|
46
|
+
"host": host,
|
|
47
|
+
"user": user,
|
|
48
|
+
"password": password,
|
|
49
|
+
"database": database,
|
|
50
|
+
"driver": driver,
|
|
51
|
+
"port": port,
|
|
52
|
+
"oracle_sid": oracle_sid,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
self.db_type = db_type or os.getenv("DB_TYPE").lower()
|
|
56
|
+
self.host = host or os.getenv("DB_HOST")
|
|
57
|
+
self.user = user or os.getenv("DB_USER")
|
|
58
|
+
self.password = password or os.getenv("DB_PASSWORD")
|
|
59
|
+
self.database = database or os.getenv("DB_NAME")
|
|
60
|
+
self.driver = driver or os.getenv("DB_DRIVER")
|
|
61
|
+
self.port = port or os.getenv("DB_PORT")
|
|
62
|
+
self.oracle_sid = oracle_sid or os.getenv("ORACLE_SID")
|
|
23
63
|
self.pool = None
|
|
24
64
|
|
|
65
|
+
if not self.db_type or not self.database:
|
|
66
|
+
raise ValueError("Missing required database configuration.")
|
|
67
|
+
|
|
25
68
|
if self.db_type == "sqlite":
|
|
26
69
|
import sqlite3
|
|
27
70
|
self.connection = sqlite3.connect(self.database)
|
|
@@ -47,6 +90,13 @@ class SQLPyHelper:
|
|
|
47
90
|
|
|
48
91
|
self.cursor = self.connection.cursor()
|
|
49
92
|
|
|
93
|
+
def __enter__(self):
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
97
|
+
self.close()
|
|
98
|
+
return False
|
|
99
|
+
|
|
50
100
|
def execute_query(self, query, params=None):
|
|
51
101
|
"""Executes a query with optional parameters"""
|
|
52
102
|
try:
|
|
@@ -61,32 +111,32 @@ class SQLPyHelper:
|
|
|
61
111
|
self.cursor.execute(query, params)
|
|
62
112
|
self.connection.commit()
|
|
63
113
|
else:
|
|
64
|
-
|
|
114
|
+
raise QueryError(f"Query failed: {e}") from e
|
|
65
115
|
|
|
66
116
|
def fetch_one(self):
|
|
67
117
|
"""Fetches a single row"""
|
|
68
118
|
try:
|
|
69
119
|
return self.cursor.fetchone()
|
|
70
120
|
except Exception as e:
|
|
71
|
-
|
|
72
|
-
return None
|
|
121
|
+
raise QueryError(f"Failed to fetch row: {e}") from e
|
|
73
122
|
|
|
74
123
|
def fetch_all(self):
|
|
75
124
|
"""Fetches all rows from the last executed query"""
|
|
76
125
|
try:
|
|
77
126
|
return self.cursor.fetchall()
|
|
78
127
|
except Exception as e:
|
|
79
|
-
|
|
80
|
-
return None
|
|
128
|
+
raise QueryError(f"Failed to fetch rows: {e}") from e
|
|
81
129
|
|
|
82
130
|
def fetch_by_param(self, table_name, column_name, value):
|
|
83
131
|
try:
|
|
84
|
-
|
|
132
|
+
table_name = _validate_identifier(table_name)
|
|
133
|
+
column_name = _validate_identifier(column_name)
|
|
134
|
+
placeholder = "?" if self.db_type == "sqlite" else "%s"
|
|
135
|
+
query = f"SELECT * FROM {table_name} WHERE {column_name} = {placeholder}"
|
|
85
136
|
self.cursor.execute(query, (value,))
|
|
86
137
|
return self.cursor.fetchall()
|
|
87
138
|
except Exception as e:
|
|
88
|
-
|
|
89
|
-
return None
|
|
139
|
+
raise QueryError(f"Failed to fetch by param: {e}") from e
|
|
90
140
|
|
|
91
141
|
def close(self):
|
|
92
142
|
"""Closes the connection"""
|
|
@@ -94,8 +144,7 @@ class SQLPyHelper:
|
|
|
94
144
|
self.cursor.close()
|
|
95
145
|
self.connection.close()
|
|
96
146
|
except Exception as e:
|
|
97
|
-
|
|
98
|
-
return None
|
|
147
|
+
raise ConnectionError(f"Failed to close connection: {e}") from e
|
|
99
148
|
|
|
100
149
|
def create_table(self, table_name, columns):
|
|
101
150
|
"""
|
|
@@ -104,12 +153,13 @@ class SQLPyHelper:
|
|
|
104
153
|
columns = {'id': 'INTEGER PRIMARY KEY', 'name': 'TEXT', 'age': 'INTEGER'}
|
|
105
154
|
"""
|
|
106
155
|
try:
|
|
107
|
-
|
|
108
|
-
|
|
156
|
+
table_name = _validate_identifier(table_name)
|
|
157
|
+
validated_cols = {_validate_identifier(col): dtype for col, dtype in columns.items()}
|
|
158
|
+
columns_def = ", ".join([f"{col} {dtype}" for col, dtype in validated_cols.items()])
|
|
159
|
+
query = f"CREATE TABLE IF NOT EXISTS {table_name} ({columns_def})"
|
|
109
160
|
self.execute_query(query)
|
|
110
161
|
except Exception as e:
|
|
111
|
-
|
|
112
|
-
return None
|
|
162
|
+
raise QueryError(f"Failed to create table: {e}") from e
|
|
113
163
|
|
|
114
164
|
def insert_bulk(self, table_name, data):
|
|
115
165
|
"""
|
|
@@ -118,16 +168,18 @@ class SQLPyHelper:
|
|
|
118
168
|
data = [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
|
|
119
169
|
"""
|
|
120
170
|
try:
|
|
121
|
-
|
|
122
|
-
|
|
171
|
+
table_name = _validate_identifier(table_name)
|
|
172
|
+
col_names = [_validate_identifier(col) for col in data[0].keys()]
|
|
173
|
+
columns = ", ".join(col_names)
|
|
174
|
+
placeholder = "?" if self.db_type == "sqlite" else "%s"
|
|
175
|
+
placeholders = ", ".join([placeholder] * len(data[0]))
|
|
123
176
|
query = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
|
|
124
177
|
values = [tuple(row.values()) for row in data]
|
|
125
178
|
self.cursor.executemany(query, values)
|
|
126
179
|
self.connection.commit()
|
|
127
180
|
|
|
128
181
|
except Exception as e:
|
|
129
|
-
|
|
130
|
-
return None
|
|
182
|
+
raise QueryError(f"Failed to insert bulk rows: {e}") from e
|
|
131
183
|
|
|
132
184
|
def backup_table(self, table_name, backup_file):
|
|
133
185
|
"""
|
|
@@ -136,6 +188,7 @@ class SQLPyHelper:
|
|
|
136
188
|
backup_table('users', 'users_backup.csv')
|
|
137
189
|
"""
|
|
138
190
|
try:
|
|
191
|
+
table_name = _validate_identifier(table_name)
|
|
139
192
|
query = f"SELECT * FROM {table_name}"
|
|
140
193
|
self.execute_query(query)
|
|
141
194
|
rows = self.fetch_all()
|
|
@@ -145,8 +198,7 @@ class SQLPyHelper:
|
|
|
145
198
|
writer.writerow([desc[0] for desc in self.cursor.description]) # Column headers
|
|
146
199
|
writer.writerows(rows)
|
|
147
200
|
except Exception as e:
|
|
148
|
-
|
|
149
|
-
return None
|
|
201
|
+
raise BackupError(f"Failed to backup table: {e}") from e
|
|
150
202
|
|
|
151
203
|
def setup_connection_pool(self, min_conn=1, max_conn=5, pool_size=5):
|
|
152
204
|
"""Sets up connection pooling based on the database type"""
|
|
@@ -182,28 +234,51 @@ class SQLPyHelper:
|
|
|
182
234
|
else:
|
|
183
235
|
raise ValueError(f"Connection pooling not supported for {self.db_type}")
|
|
184
236
|
except Exception as e:
|
|
185
|
-
|
|
186
|
-
self.pool = None # Prevent broken pool usage
|
|
237
|
+
raise ConnectionError(f"Failed to set up connection pool: {e}") from e
|
|
187
238
|
|
|
188
239
|
def get_connection_from_pool(self):
|
|
189
240
|
"""Fetches a connection from the pool."""
|
|
190
241
|
return self.pool.get_connection()
|
|
191
242
|
|
|
192
|
-
def return_connection_to_pool(self):
|
|
243
|
+
def return_connection_to_pool(self, connection=None) -> None:
|
|
193
244
|
"""Returns a connection back to the pool."""
|
|
194
|
-
self.connection
|
|
245
|
+
conn = connection or self.connection
|
|
246
|
+
if self.pool is None:
|
|
247
|
+
raise RuntimeError("No connection pool initialised. Call setup_connection_pool() first.")
|
|
248
|
+
|
|
249
|
+
if self.db_type == "postgres":
|
|
250
|
+
self.pool.putconn(conn)
|
|
251
|
+
elif self.db_type == "mysql":
|
|
252
|
+
conn.close()
|
|
253
|
+
elif self.db_type == "oracle":
|
|
254
|
+
self.pool.release(conn)
|
|
255
|
+
else:
|
|
256
|
+
conn.close()
|
|
195
257
|
|
|
196
258
|
def reconnect(self):
|
|
197
259
|
"""Reconnects to the database if connection is lost"""
|
|
198
260
|
try:
|
|
199
|
-
self.connection.close()
|
|
200
|
-
self.__init__()
|
|
261
|
+
self.connection.close()
|
|
262
|
+
self.__init__(**self._init_kwargs)
|
|
201
263
|
print("Database reconnected successfully.")
|
|
202
264
|
except Exception as e:
|
|
203
|
-
|
|
265
|
+
raise ConnectionError(f"Reconnection failed: {e}") from e
|
|
204
266
|
|
|
205
267
|
def begin_transaction(self):
|
|
206
268
|
self.execute_query("START TRANSACTION")
|
|
207
269
|
|
|
208
270
|
def rollback_transaction(self):
|
|
209
271
|
self.execute_query("ROLLBACK")
|
|
272
|
+
|
|
273
|
+
def insert_dynamic(self, table, data: dict):
|
|
274
|
+
"""
|
|
275
|
+
Dynamically constructs and executes an INSERT query with database-specific placeholders.
|
|
276
|
+
"""
|
|
277
|
+
table = _validate_identifier(table)
|
|
278
|
+
columns = ", ".join(_validate_identifier(col) for col in data.keys())
|
|
279
|
+
placeholders_style = "?" if self.db_type == "sqlite" else "%s"
|
|
280
|
+
placeholders = ", ".join([placeholders_style] * len(data))
|
|
281
|
+
values = tuple(data.values())
|
|
282
|
+
|
|
283
|
+
sql = f"INSERT INTO {table} ({columns}) VALUES ({placeholders})"
|
|
284
|
+
self.execute_query(sql, values)
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from sqlpyhelper.automation_utils import AutomationUtils
|
|
2
|
+
|
|
3
|
+
utils = AutomationUtils()
|
|
4
|
+
|
|
5
|
+
print("✅ Loading test data...")
|
|
6
|
+
utils.load_data_from_csv("sample_data.csv", "contributors")
|
|
7
|
+
|
|
8
|
+
print("\n📊 Contribution breakdown:")
|
|
9
|
+
print(utils.aggregate_column("contributors", "contribution", "name", "timestamp"))
|
|
10
|
+
|
|
11
|
+
print("\n⚠️ Missing months:")
|
|
12
|
+
print(utils.detect_missing_periods("contributors", "name", "timestamp"))
|
|
13
|
+
|
|
14
|
+
print("\n🚨 Outliers:")
|
|
15
|
+
print(utils.detect_outliers("contributors", "contribution"))
|
sqlpyhelper-0.1.3/setup.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
from setuptools import setup, find_packages
|
|
2
|
-
|
|
3
|
-
with open("README.md", "r", encoding="utf-8") as f:
|
|
4
|
-
long_description = f.read()
|
|
5
|
-
|
|
6
|
-
setup(
|
|
7
|
-
name='SQLPyHelper',
|
|
8
|
-
version='0.1.3',
|
|
9
|
-
description='A simple SQL database helper package for Python.',
|
|
10
|
-
long_description=long_description,
|
|
11
|
-
long_description_content_type="text/markdown",
|
|
12
|
-
author='Adebayo Olaonipekun',
|
|
13
|
-
author_email='pekunmi@live.com',
|
|
14
|
-
packages=find_packages(),
|
|
15
|
-
install_requires=[
|
|
16
|
-
'psycopg2',
|
|
17
|
-
'mysql-connector-python',
|
|
18
|
-
'pyodbc',
|
|
19
|
-
'cx_Oracle',
|
|
20
|
-
'python-dotenv'
|
|
21
|
-
],
|
|
22
|
-
extras_require={
|
|
23
|
-
"mysql": ["mysql-connector-python"],
|
|
24
|
-
"postgres": ["psycopg2"],
|
|
25
|
-
"oracle": ["cx_Oracle"],
|
|
26
|
-
"sqlserver": ["pyodbc"],
|
|
27
|
-
"sqlite": []
|
|
28
|
-
},
|
|
29
|
-
python_requires=">=3.8",
|
|
30
|
-
classifiers=[
|
|
31
|
-
"Programming Language :: Python :: 3",
|
|
32
|
-
"Development Status :: 5 - Production/Stable",
|
|
33
|
-
"Intended Audience :: Developers",
|
|
34
|
-
"Topic :: Database :: Database Engines/Servers",
|
|
35
|
-
"Operating System :: OS Independent",
|
|
36
|
-
"License :: OSI Approved :: MIT License"
|
|
37
|
-
],
|
|
38
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|