dbtk 0.8.0__py3-none-any.whl

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.
Files changed (50) hide show
  1. dbtk/__init__.py +54 -0
  2. dbtk/cli.py +169 -0
  3. dbtk/config.py +1194 -0
  4. dbtk/cursors.py +566 -0
  5. dbtk/database.py +959 -0
  6. dbtk/dbtk_sample.yml +90 -0
  7. dbtk/defaults.py +29 -0
  8. dbtk/etl/__init__.py +61 -0
  9. dbtk/etl/base_surge.py +257 -0
  10. dbtk/etl/bulk_surge.py +783 -0
  11. dbtk/etl/config_generators.py +458 -0
  12. dbtk/etl/data_surge.py +294 -0
  13. dbtk/etl/managers.py +734 -0
  14. dbtk/etl/table.py +1215 -0
  15. dbtk/etl/transforms/__init__.py +107 -0
  16. dbtk/etl/transforms/address.py +560 -0
  17. dbtk/etl/transforms/core.py +594 -0
  18. dbtk/etl/transforms/database.py +506 -0
  19. dbtk/etl/transforms/datetime.py +497 -0
  20. dbtk/etl/transforms/email.py +72 -0
  21. dbtk/etl/transforms/phone.py +670 -0
  22. dbtk/formats/__init__.py +18 -0
  23. dbtk/formats/edi.py +182 -0
  24. dbtk/logging_utils.py +310 -0
  25. dbtk/readers/__init__.py +26 -0
  26. dbtk/readers/base.py +644 -0
  27. dbtk/readers/csv.py +195 -0
  28. dbtk/readers/data_frame.py +119 -0
  29. dbtk/readers/excel.py +260 -0
  30. dbtk/readers/fixed_width.py +359 -0
  31. dbtk/readers/json.py +323 -0
  32. dbtk/readers/utils.py +394 -0
  33. dbtk/readers/xml.py +260 -0
  34. dbtk/record.py +710 -0
  35. dbtk/utils.py +537 -0
  36. dbtk/writers/__init__.py +46 -0
  37. dbtk/writers/base.py +718 -0
  38. dbtk/writers/csv.py +107 -0
  39. dbtk/writers/database.py +158 -0
  40. dbtk/writers/excel.py +1086 -0
  41. dbtk/writers/fixed_width.py +290 -0
  42. dbtk/writers/json.py +156 -0
  43. dbtk/writers/utils.py +41 -0
  44. dbtk/writers/xml.py +387 -0
  45. dbtk-0.8.0.dist-info/METADATA +305 -0
  46. dbtk-0.8.0.dist-info/RECORD +50 -0
  47. dbtk-0.8.0.dist-info/WHEEL +5 -0
  48. dbtk-0.8.0.dist-info/entry_points.txt +2 -0
  49. dbtk-0.8.0.dist-info/licenses/LICENSE.txt +7 -0
  50. dbtk-0.8.0.dist-info/top_level.txt +1 -0
dbtk/__init__.py ADDED
@@ -0,0 +1,54 @@
1
+ # dbtk/__init__.py
2
+ """
3
+ DBTK - Data Benders ToolKit
4
+
5
+ A lightweight database integration toolkit that provides:
6
+ - Uniform interface across different databases (PostgreSQL, Oracle, MySQL, SQL Server, SQLite)
7
+ - Flexible cursor types returning different data structures
8
+ - YAML-based configuration with password encryption
9
+ - Writers for CSV, Excel, fixed-width, and database-to-database export
10
+ - Context managers for connections and transactions
11
+
12
+ Basic usage::
13
+
14
+ import dbtk
15
+
16
+ # From YAML config file
17
+ with dbtk.connect('prod_warehouse') as db:
18
+ cursor = db.cursor()
19
+ cursor.execute("SELECT * FROM users")
20
+
21
+ # Export results
22
+ dbtk.writers.to_csv(cursor, 'users.csv')
23
+ dbtk.writers.to_excel(cursor, 'report.xlsx')
24
+
25
+ Direct connections:
26
+ from dbtk.database import postgres, oracle
27
+
28
+ db = postgres(user='user', password='pass', database='db')
29
+ cursor = db.cursor() # Returns Record objects
30
+ """
31
+
32
+ __version__ = '0.8.0'
33
+ __author__ = 'Scott Bailey <scottrbailey@gmail.com>'
34
+
35
+ from .database import Database
36
+ from .config import connect, set_config_file
37
+ from .cursors import Cursor
38
+ from .logging_utils import setup_logging, cleanup_old_logs, errors_logged
39
+ from . import etl
40
+ from . import readers
41
+ from . import writers
42
+
43
+ # Simple, clean exports
44
+ __all__ = [
45
+ 'connect',
46
+ 'Database',
47
+ 'Cursor',
48
+ 'etl',
49
+ 'readers',
50
+ 'writers',
51
+ 'setup_logging',
52
+ 'cleanup_old_logs',
53
+ 'errors_logged'
54
+ ]
dbtk/cli.py ADDED
@@ -0,0 +1,169 @@
1
+ # dbtk/cli.py
2
+
3
+ import argparse
4
+ import importlib.util
5
+ import sys
6
+ from .database import _get_all_drivers
7
+ from . import config
8
+
9
+ try:
10
+ from importlib.metadata import metadata, distributions, requires
11
+ except ImportError:
12
+ # backport to older versions
13
+ from importlib_metadata import metadata, distributions, requires
14
+
15
+
16
+ def _name_cleanup(name):
17
+ """Cleanup module names for search and display"""
18
+ return name.lower().replace('-', '_')
19
+
20
+
21
+ def _get_optional_deps(extra_name='recommended'):
22
+ """ Get optional dependencies for dbtk """
23
+ reqs = requires('dbtk') or []
24
+ deps = []
25
+ # Parse requirements like: 'psycopg2-binary>=2.8; extra == "recommended"'
26
+ for req in reqs:
27
+ req = req.replace("'", '"') # quoting changed between versions
28
+ if f'extra == "{extra_name}"' in req:
29
+ # Extract just the package name and version spec
30
+ pkg = req.split(';')[0].strip()
31
+ deps.append(pkg)
32
+ return deps
33
+
34
+
35
+ def _is_installed(pkg: str) -> bool:
36
+ """find_spec fails on tomli → this never does."""
37
+ pkg = _name_cleanup(pkg)
38
+ return (
39
+ importlib.util.find_spec(pkg) is not None
40
+ or pkg in sys.modules
41
+ or pkg in {_name_cleanup(d.name) for d in distributions()}
42
+ )
43
+
44
+ def checkup():
45
+ """ Check which optional dependencies are installed."""
46
+ deps = []
47
+ for dep in _get_optional_deps('recommended'):
48
+ if ';' in dep:
49
+ dep = dep.split(';')[0].strip()
50
+ if dep and not dep.startswith('#'):
51
+ deps.append(dep.split('>=')[0].split('==')[0].split('<')[0].strip())
52
+
53
+ installed = {_name_cleanup(d.name): d.version for d in distributions()}
54
+
55
+ print(f"{'Package':<20} {'Status':<8} {'Version'}")
56
+ print("-" * 40)
57
+
58
+ for dep in deps:
59
+ clean = dep.replace('-', '_')
60
+ status = "✓" if _is_installed(clean) else "✗"
61
+ version = installed.get(clean.lower(), '-')
62
+ print(f"{dep:<20} {status:<8} {version}")
63
+
64
+ print("\nDB Drivers Priority* Status Version")
65
+ print("-" * 56)
66
+ all_drivers = _get_all_drivers()
67
+ by_type = {}
68
+
69
+ for name, info in all_drivers.items():
70
+ db_type = info['database_type']
71
+ by_type.setdefault(db_type, []).append((info['priority'], name, info))
72
+
73
+ if _is_installed('pyodbc'):
74
+ import pyodbc
75
+ odbc_drivers = pyodbc.drivers()
76
+ else:
77
+ odbc_drivers = []
78
+
79
+ for db_type in sorted(by_type):
80
+ drivers = sorted(by_type[db_type], key=lambda x: x[0])
81
+ print(f"{db_type}") # ← bold header
82
+ for pri, name, info in drivers:
83
+ # ── 2-space indent for hierarchy
84
+ display_name = f" {name}" # ← indented
85
+
86
+ try:
87
+ # see if driver has 'module' attribute to use instead of name
88
+ module_name = info.get('module', name)
89
+ spec = importlib.util.find_spec(module_name)
90
+ version = installed.get(_name_cleanup(module_name), '-- ')
91
+ status = "✓" if spec else "✗"
92
+ except ModuleNotFoundError:
93
+ version = '-- '
94
+ status = "✗"
95
+ odbc_driver_name = info.get("odbc_driver_name")
96
+ if odbc_driver_name:
97
+ odbc_status = "✓" if odbc_driver_name in odbc_drivers else "✗"
98
+ note = f'({odbc_status} {odbc_driver_name})'
99
+ else:
100
+ if 'note' in info:
101
+ note = f'({info.get("note")})'
102
+ else:
103
+ note = ''
104
+
105
+ print(f"{display_name:<20} {pri:<9} {status:<8} {version} {note}")
106
+
107
+ print("\n* Lower priority = preferred")
108
+
109
+ print("\nConfig Health")
110
+ print("-" * 40)
111
+ for status, msg in config.diagnose_config():
112
+ print(f"{status} {msg}")
113
+
114
+
115
+ def main():
116
+ parser = argparse.ArgumentParser(prog='dbtk', description='DBTK command-line utilities')
117
+ subparsers = parser.add_subparsers(dest='command', required=True)
118
+
119
+ # checkup
120
+ subparsers.add_parser('checkup', help='Check for dependencies and configuration issues')
121
+
122
+ # config-setup
123
+ subparsers.add_parser('config-setup', help='Interactive configuration setup wizard')
124
+
125
+ # generate-key
126
+ subparsers.add_parser('generate-key', help='Generate encryption key')
127
+
128
+ # store-key
129
+ key_parser = subparsers.add_parser('store-key',
130
+ help='Store encryption key in system keyring (generate if not provided)')
131
+ key_parser.add_argument('key', nargs='?', default=None,
132
+ help='Encryption key to store. If omitted, a new key is generated and stored.')
133
+ key_parser.add_argument('--force', action='store_true',
134
+ help='Overwrite existing encryption key in system keyring')
135
+
136
+ # encrypt-config
137
+ encrypt_parser = subparsers.add_parser('encrypt-config', help='Encrypt passwords in config file')
138
+ encrypt_parser.add_argument('config_file', nargs='?', help='Config file path')
139
+
140
+ # encrypt-password
141
+ pwd_parser = subparsers.add_parser('encrypt-password', help='Encrypt a password')
142
+ pwd_parser.add_argument('password', nargs='?', help='Password to encrypt')
143
+
144
+ # migrate-config
145
+ migrate_parser = subparsers.add_parser('migrate-config', help='Migrate config to new key')
146
+ migrate_parser.add_argument('old_file', help='Old config file')
147
+ migrate_parser.add_argument('new_file', help='New config file')
148
+ migrate_parser.add_argument('--new-key', help='New encryption key')
149
+
150
+ args = parser.parse_args()
151
+
152
+ if args.command == 'checkup':
153
+ return checkup()
154
+ elif args.command == 'config-setup':
155
+ return config.setup_config()
156
+ elif args.command == 'generate-key':
157
+ return config.generate_encryption_key()
158
+ elif args.command == 'store-key':
159
+ return config.store_key(args.key)
160
+ elif args.command == 'encrypt-config':
161
+ return config.encrypt_config_file(args.config_file)
162
+ elif args.command == 'encrypt-password':
163
+ return config.encrypt_password(args.password)
164
+ elif args.command == 'migrate-config':
165
+ return config.migrate_config(args.old_file, args.new_file, args.new_key)
166
+
167
+
168
+ if __name__ == '__main__':
169
+ main()