fullscript.spellbook 2.1__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.
- fullscript.spellbook-2.1.dist-info/LICENSE +21 -0
- fullscript.spellbook-2.1.dist-info/METADATA +208 -0
- fullscript.spellbook-2.1.dist-info/RECORD +23 -0
- fullscript.spellbook-2.1.dist-info/WHEEL +5 -0
- fullscript.spellbook-2.1.dist-info/top_level.txt +1 -0
- spellbook/__init__.py +0 -0
- spellbook/db_magic.py +266 -0
- spellbook/drive_magic.py +266 -0
- spellbook/sheet_magic.py +181 -0
- spellbook/stats_magic.py +211 -0
- spellbook/utils.py +93 -0
- spellbook/wizards/wizard1.txt +11 -0
- spellbook/wizards/wizard10.txt +18 -0
- spellbook/wizards/wizard11.txt +14 -0
- spellbook/wizards/wizard12.txt +13 -0
- spellbook/wizards/wizard2.txt +2 -0
- spellbook/wizards/wizard3.txt +2 -0
- spellbook/wizards/wizard4.txt +7 -0
- spellbook/wizards/wizard5.txt +4 -0
- spellbook/wizards/wizard6.txt +3 -0
- spellbook/wizards/wizard7.txt +4 -0
- spellbook/wizards/wizard8.txt +22 -0
- spellbook/wizards/wizard9.txt +24 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Fullscript
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: fullscript.spellbook
|
|
3
|
+
Version: 2.1
|
|
4
|
+
Summary: A simple package to make your life easier.
|
|
5
|
+
Author: Gui Chagas, David King, Meinhard Mare, Karsten Melchers
|
|
6
|
+
Author-email: Thomas Edwards <thomas.edwards@fullscript.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: homepage, https://git.fullscript.io/data/spellbook
|
|
9
|
+
Requires-Python: <3.12,>=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: appnope==0.1.4
|
|
13
|
+
Requires-Dist: asn1crypto==1.5.1
|
|
14
|
+
Requires-Dist: asttokens==2.4.1
|
|
15
|
+
Requires-Dist: snowflake-sqlalchemy==1.7.0
|
|
16
|
+
Requires-Dist: certifi==2024.8.30
|
|
17
|
+
Requires-Dist: cffi==1.17.1
|
|
18
|
+
Requires-Dist: charset-normalizer==3.4.0
|
|
19
|
+
Requires-Dist: cloudpickle==2.2.1
|
|
20
|
+
Requires-Dist: comm==0.2.2
|
|
21
|
+
Requires-Dist: cryptography==43.0.3
|
|
22
|
+
Requires-Dist: debugpy==1.8.8
|
|
23
|
+
Requires-Dist: decorator==5.1.1
|
|
24
|
+
Requires-Dist: executing==2.1.0
|
|
25
|
+
Requires-Dist: filelock==3.16.1
|
|
26
|
+
Requires-Dist: idna==3.10
|
|
27
|
+
Requires-Dist: ipykernel==6.29.5
|
|
28
|
+
Requires-Dist: ipython==8.29.0
|
|
29
|
+
Requires-Dist: jedi==0.19.2
|
|
30
|
+
Requires-Dist: jupyter_client==8.6.3
|
|
31
|
+
Requires-Dist: jupyter_core==5.7.2
|
|
32
|
+
Requires-Dist: matplotlib-inline==0.1.7
|
|
33
|
+
Requires-Dist: nest-asyncio==1.6.0
|
|
34
|
+
Requires-Dist: numpy==2.1.3
|
|
35
|
+
Requires-Dist: packaging==24.2
|
|
36
|
+
Requires-Dist: pandas==2.2.3
|
|
37
|
+
Requires-Dist: parso==0.8.4
|
|
38
|
+
Requires-Dist: pexpect==4.9.0
|
|
39
|
+
Requires-Dist: platformdirs==4.3.6
|
|
40
|
+
Requires-Dist: prompt_toolkit==3.0.48
|
|
41
|
+
Requires-Dist: protobuf==5.28.3
|
|
42
|
+
Requires-Dist: psutil==6.1.0
|
|
43
|
+
Requires-Dist: ptyprocess==0.7.0
|
|
44
|
+
Requires-Dist: pure_eval==0.2.3
|
|
45
|
+
Requires-Dist: pyarrow==18.0.0
|
|
46
|
+
Requires-Dist: pycparser==2.22
|
|
47
|
+
Requires-Dist: Pygments==2.18.0
|
|
48
|
+
Requires-Dist: PyJWT==2.9.0
|
|
49
|
+
Requires-Dist: pyOpenSSL==24.2.1
|
|
50
|
+
Requires-Dist: python-dateutil==2.9.0.post0
|
|
51
|
+
Requires-Dist: pytz==2024.2
|
|
52
|
+
Requires-Dist: PyYAML==6.0.2
|
|
53
|
+
Requires-Dist: pyzmq==26.2.0
|
|
54
|
+
Requires-Dist: requests==2.32.3
|
|
55
|
+
Requires-Dist: six==1.16.0
|
|
56
|
+
Requires-Dist: snowflake-connector-python==3.12.3
|
|
57
|
+
Requires-Dist: snowflake-snowpark-python==1.25.0
|
|
58
|
+
Requires-Dist: sortedcontainers==2.4.0
|
|
59
|
+
Requires-Dist: SQLAlchemy==2.0.36
|
|
60
|
+
Requires-Dist: stack-data==0.6.3
|
|
61
|
+
Requires-Dist: tomlkit==0.13.2
|
|
62
|
+
Requires-Dist: tornado==6.4.1
|
|
63
|
+
Requires-Dist: traitlets==5.14.3
|
|
64
|
+
Requires-Dist: typing_extensions==4.12.2
|
|
65
|
+
Requires-Dist: tzdata==2024.2
|
|
66
|
+
Requires-Dist: tzlocal==5.2
|
|
67
|
+
Requires-Dist: urllib3==2.2.3
|
|
68
|
+
Requires-Dist: wcwidth==0.2.13
|
|
69
|
+
Provides-Extra: drivemagic
|
|
70
|
+
Requires-Dist: cachetools==5.5.0; extra == "drivemagic"
|
|
71
|
+
Requires-Dist: google-api-core==2.23.0; extra == "drivemagic"
|
|
72
|
+
Requires-Dist: google-api-python-client==2.153.0; extra == "drivemagic"
|
|
73
|
+
Requires-Dist: google-auth==2.36.0; extra == "drivemagic"
|
|
74
|
+
Requires-Dist: google-auth-httplib2==0.2.0; extra == "drivemagic"
|
|
75
|
+
Requires-Dist: google-auth-oauthlib==1.2.1; extra == "drivemagic"
|
|
76
|
+
Requires-Dist: googleapis-common-protos==1.66.0; extra == "drivemagic"
|
|
77
|
+
Requires-Dist: httplib2==0.22.0; extra == "drivemagic"
|
|
78
|
+
Requires-Dist: oauthlib==3.2.2; extra == "drivemagic"
|
|
79
|
+
Requires-Dist: proto-plus==1.25.0; extra == "drivemagic"
|
|
80
|
+
Requires-Dist: pyasn1==0.6.1; extra == "drivemagic"
|
|
81
|
+
Requires-Dist: pyasn1_modules==0.4.1; extra == "drivemagic"
|
|
82
|
+
Requires-Dist: pyparsing==3.2.0; extra == "drivemagic"
|
|
83
|
+
Requires-Dist: requests-oauthlib==2.0.0; extra == "drivemagic"
|
|
84
|
+
Requires-Dist: rsa==4.9; extra == "drivemagic"
|
|
85
|
+
Requires-Dist: uritemplate==4.1.1; extra == "drivemagic"
|
|
86
|
+
Provides-Extra: sheetmagic
|
|
87
|
+
Requires-Dist: cachetools==5.5.0; extra == "sheetmagic"
|
|
88
|
+
Requires-Dist: google-api-core==2.23.0; extra == "sheetmagic"
|
|
89
|
+
Requires-Dist: google-api-python-client==2.153.0; extra == "sheetmagic"
|
|
90
|
+
Requires-Dist: google-auth==2.36.0; extra == "sheetmagic"
|
|
91
|
+
Requires-Dist: google-auth-httplib2==0.2.0; extra == "sheetmagic"
|
|
92
|
+
Requires-Dist: google-auth-oauthlib==1.2.1; extra == "sheetmagic"
|
|
93
|
+
Requires-Dist: googleapis-common-protos==1.66.0; extra == "sheetmagic"
|
|
94
|
+
Requires-Dist: httplib2==0.22.0; extra == "sheetmagic"
|
|
95
|
+
Requires-Dist: oauthlib==3.2.2; extra == "sheetmagic"
|
|
96
|
+
Requires-Dist: pyasn1==0.6.1; extra == "sheetmagic"
|
|
97
|
+
Requires-Dist: pyasn1_modules==0.4.1; extra == "sheetmagic"
|
|
98
|
+
Requires-Dist: Pygments==2.18.0; extra == "sheetmagic"
|
|
99
|
+
Requires-Dist: pygsheets==2.0.6; extra == "sheetmagic"
|
|
100
|
+
Requires-Dist: pyparsing==3.2.0; extra == "sheetmagic"
|
|
101
|
+
Requires-Dist: requests-oauthlib==2.0.0; extra == "sheetmagic"
|
|
102
|
+
Requires-Dist: rsa==4.9; extra == "sheetmagic"
|
|
103
|
+
Requires-Dist: uritemplate==4.1.1; extra == "sheetmagic"
|
|
104
|
+
Provides-Extra: statsmagic
|
|
105
|
+
Requires-Dist: contourpy==1.3.1; extra == "statsmagic"
|
|
106
|
+
Requires-Dist: cycler==0.12.1; extra == "statsmagic"
|
|
107
|
+
Requires-Dist: fonttools==4.55.0; extra == "statsmagic"
|
|
108
|
+
Requires-Dist: kiwisolver==1.4.7; extra == "statsmagic"
|
|
109
|
+
Requires-Dist: matplotlib==3.9.2; extra == "statsmagic"
|
|
110
|
+
Requires-Dist: patsy==1.0.1; extra == "statsmagic"
|
|
111
|
+
Requires-Dist: pillow==11.0.0; extra == "statsmagic"
|
|
112
|
+
Requires-Dist: pyparsing==3.2.0; extra == "statsmagic"
|
|
113
|
+
Requires-Dist: python-dateutil==2.9.0.post0; extra == "statsmagic"
|
|
114
|
+
Requires-Dist: pytz==2024.2; extra == "statsmagic"
|
|
115
|
+
Requires-Dist: scikit-posthocs==0.10.0; extra == "statsmagic"
|
|
116
|
+
Requires-Dist: scipy==1.14.1; extra == "statsmagic"
|
|
117
|
+
Requires-Dist: seaborn==0.13.2; extra == "statsmagic"
|
|
118
|
+
Requires-Dist: six==1.16.0; extra == "statsmagic"
|
|
119
|
+
Requires-Dist: statsmodels==0.14.4; extra == "statsmagic"
|
|
120
|
+
|
|
121
|
+
# Spellbook
|
|
122
|
+
|
|
123
|
+
Helping data scientists and engineers save time by making it simple to connect to databases, work with spreadsheets,
|
|
124
|
+
manage files, and run analyses. With Spellbook, they can focus on finding insights and solving problems instead of
|
|
125
|
+
dealing with complicated tools.
|
|
126
|
+
|
|
127
|
+
With Spellbook, you can:
|
|
128
|
+
* Seamlessly connect to Snowflake and MySQL databases, for secure querying and effortless data retrieval.
|
|
129
|
+
* Work with Google Sheets, making it easy to read, write, and update spreadsheets.
|
|
130
|
+
* Manage Google Drive, creating folders and saving files with ease.
|
|
131
|
+
* Perform statistical analyses, helping you run A/B tests and extract actionable insights.
|
|
132
|
+
|
|
133
|
+
Spellbook is built to simplify your workflows and supercharge your data exploration, so you can focus on turning insights into action!
|
|
134
|
+
|
|
135
|
+
## Table of Contents
|
|
136
|
+
|
|
137
|
+
- [Features](#features)
|
|
138
|
+
- [Installation](#installation)
|
|
139
|
+
- [Configuration](#configuration)
|
|
140
|
+
- [Contributing](#contributing)
|
|
141
|
+
- [License](#license)
|
|
142
|
+
|
|
143
|
+
## Features
|
|
144
|
+
|
|
145
|
+
- Effortless Database Connections and query execution
|
|
146
|
+
- Read/ Write to Google worksheets
|
|
147
|
+
- Manage Google Drive
|
|
148
|
+
- Perform statistical analyses
|
|
149
|
+
|
|
150
|
+
For feature methods and examples, please refer to the [Wiki Page](https://git.fullscript.io/data/spellbook/-/wikis/home).
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
## Installation
|
|
154
|
+
|
|
155
|
+
Spellbook requires Python 3.11 or higher. Use the following steps to install:
|
|
156
|
+
> Note: At the time relase, Snowpark does not support versions of Python higher than 3.11
|
|
157
|
+
|
|
158
|
+
1. Pip install:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
pip install git+https://git.fullscript.io/data/spellbook.git
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Configuration
|
|
165
|
+
Effortlessly and securely manage your connections and configurations! Simply add database types and connection
|
|
166
|
+
credentials to your [spellbook_config.yaml](spellbook_config.yaml) file, or include your Google service account details for seamless access to
|
|
167
|
+
Google services. For added security, Spellbook supports storing credentials in environment variables
|
|
168
|
+
or in a configuration file that references those variables, keeping your sensitive information safe and sound.
|
|
169
|
+
|
|
170
|
+
You must have a `spellbook_config.yaml` file in your root directory or in your project directory.
|
|
171
|
+
An example of the `spellbook_config.yaml` file is shown below:
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
databases:
|
|
175
|
+
# Snowflake Database Connection
|
|
176
|
+
- name: alias_of_your_connection
|
|
177
|
+
type: snowflake
|
|
178
|
+
account: your_account
|
|
179
|
+
user: snowflake_user_name # Optional for private key auth
|
|
180
|
+
password: snowflake_password # Optional, used if private key is not present
|
|
181
|
+
private_key: snowflake_pk # Base64-encoded private key
|
|
182
|
+
private_key_passphrase: snowflake_pk_phrase # Optional
|
|
183
|
+
role: snowflake_role
|
|
184
|
+
warehouse: snowflake_warehouse
|
|
185
|
+
database: snowflake_database
|
|
186
|
+
schema: snowflake_schema
|
|
187
|
+
|
|
188
|
+
google_accounts:
|
|
189
|
+
# Google Sheets Service Account 1
|
|
190
|
+
- name: google_sheets_service_account_1
|
|
191
|
+
service_account: "your_base64_encoded_service_account_credentials_here" # Add your Base64-encoded service account JSON here
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Contributing
|
|
195
|
+
|
|
196
|
+
We welcome contributions! Please follow these steps:
|
|
197
|
+
|
|
198
|
+
1. Fork this repository.
|
|
199
|
+
2. Create a new branch: `git checkout -b feature-name`.
|
|
200
|
+
3. Commit your changes: `git commit -m 'Add some feature'`.
|
|
201
|
+
4. Push to the branch: `git push origin feature-name`.
|
|
202
|
+
5. Open a pull request.
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
Spellbook is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
|
|
207
|
+
|
|
208
|
+
---
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
spellbook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
spellbook/db_magic.py,sha256=rRQm6EL7vRvO7rR9snF0_RlJTRfmFq9gvWvh7L9BYbM,8579
|
|
3
|
+
spellbook/drive_magic.py,sha256=uZH-nAOgBXO_NHm4OJ3xX3jPAS11jTj0bkfNd8pBLis,9594
|
|
4
|
+
spellbook/sheet_magic.py,sha256=a8ZQE3ifFJpsptfHyj4ku-hrbeBM8qL3zW5Sy-HjL_c,8186
|
|
5
|
+
spellbook/stats_magic.py,sha256=8uwq-n4NWDYzUUFZAX17Ls1jWcFj17l-BFqeF8VrTPQ,7655
|
|
6
|
+
spellbook/utils.py,sha256=DQ1R3dZxhnEvUMixvy28oyCuJw63MudwJLnCs_yIyQ8,3306
|
|
7
|
+
spellbook/wizards/wizard1.txt,sha256=6F-jH4EJ5birPbFrD4HhxIUBLtP2wTlUZcJjNV5upAg,285
|
|
8
|
+
spellbook/wizards/wizard10.txt,sha256=NjT8pIZs60nahK-7Z6mkIdUgoUNeaBj0qU4NdWrM7aM,798
|
|
9
|
+
spellbook/wizards/wizard11.txt,sha256=ioKNImgjRTQNQdmeSagmeMAnDNhldCbcLHHy9EuPG0Y,565
|
|
10
|
+
spellbook/wizards/wizard12.txt,sha256=78fVVYHbSqf5AqI7xgSZNvznpdKtKXOhzvPBaJO5Dic,462
|
|
11
|
+
spellbook/wizards/wizard2.txt,sha256=8zgsC2-msh4QGez3Ivv1VuurQBGyeFiyuVNs9VfWkA8,57
|
|
12
|
+
spellbook/wizards/wizard3.txt,sha256=QO8cQa9HsqtTaUMNDqvic8ySRBx6O1QYst2plGaqjFE,61
|
|
13
|
+
spellbook/wizards/wizard4.txt,sha256=eLBEHHiX2aCADRmlXUg5PiovZKHnbF4Uz9uM5PsasKo,146
|
|
14
|
+
spellbook/wizards/wizard5.txt,sha256=3Vm-KdTUnq34IGxd_kMeWY5j_LHlzYPvAkRtkqq1i3w,129
|
|
15
|
+
spellbook/wizards/wizard6.txt,sha256=mQ0FNaRrh-q5B6MiJZ1MO4XCdqsPvj_tGOhIZJDfHis,115
|
|
16
|
+
spellbook/wizards/wizard7.txt,sha256=ssaQtCSfUaLOgq7-gu0Ao74GFa7FkN8lCjOMIiGL4Ak,86
|
|
17
|
+
spellbook/wizards/wizard8.txt,sha256=ZVgn0KzC0g71_lnzvYf8XbSbrGEqFHS_n3JjRFEvrOg,684
|
|
18
|
+
spellbook/wizards/wizard9.txt,sha256=ROHeUtq9k66goCVsLctpPgx3U_UxAJzBtSVyWhFfho0,1435
|
|
19
|
+
fullscript.spellbook-2.1.dist-info/LICENSE,sha256=RG9zQfX4eYYNV_n6_zvsKucXCgPqSAXBUQkkPEJJUQg,1066
|
|
20
|
+
fullscript.spellbook-2.1.dist-info/METADATA,sha256=QOFJ_R6PcEPoqjJCptiK5scoqzOLl__Y3TDnjF7L2_Y,8465
|
|
21
|
+
fullscript.spellbook-2.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
22
|
+
fullscript.spellbook-2.1.dist-info/top_level.txt,sha256=mL7M9kCzo_sE_bt4FPXIdHtCV8YiDCAH8kkdOSI3yqw,10
|
|
23
|
+
fullscript.spellbook-2.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
spellbook
|
spellbook/__init__.py
ADDED
|
File without changes
|
spellbook/db_magic.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from snowflake.snowpark import Session
|
|
3
|
+
from sqlalchemy import create_engine
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from cryptography.hazmat.backends import default_backend
|
|
6
|
+
from cryptography.hazmat.primitives import serialization
|
|
7
|
+
import base64
|
|
8
|
+
from spellbook.utils import load_and_parse_config, get_wizard
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Needs a very specific pip install
|
|
12
|
+
# pip install "snowflake-connector-python[pandas]"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_db_config(db_name):
|
|
16
|
+
# load the configuration
|
|
17
|
+
try:
|
|
18
|
+
config = load_and_parse_config(configurations='databases')
|
|
19
|
+
except FileNotFoundError:
|
|
20
|
+
print("Config file not found. Please ensure 'spellbook_config.yaml' exists.")
|
|
21
|
+
config = None
|
|
22
|
+
|
|
23
|
+
for db in config['databases']:
|
|
24
|
+
if db['name'] == db_name:
|
|
25
|
+
return db
|
|
26
|
+
raise ValueError(f"Database configuration for '{db_name}' not found.")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def snowflake_connect(db_name, **kwargs):
|
|
30
|
+
"""
|
|
31
|
+
Create a Snowflake connection using credentials from the config file or environment variables.
|
|
32
|
+
:param db_name: The name of the database as defined in the config file.
|
|
33
|
+
:return: Snowpark session object.
|
|
34
|
+
"""
|
|
35
|
+
db_config = get_db_config(db_name, **kwargs) # Fetch database config from the config file
|
|
36
|
+
|
|
37
|
+
# Determine authentication method
|
|
38
|
+
use_private_key = db_config.get('private_key') or os.getenv('SNOW_PK')
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
if use_private_key:
|
|
42
|
+
# Private key authentication
|
|
43
|
+
private_key = db_config.get('private_key')
|
|
44
|
+
passphrase = db_config.get('private_key_passphrase')
|
|
45
|
+
|
|
46
|
+
# Decode and load the private key
|
|
47
|
+
p_key = serialization.load_pem_private_key(
|
|
48
|
+
base64.b64decode(private_key),
|
|
49
|
+
password=passphrase.encode('ascii') if passphrase else None,
|
|
50
|
+
backend=default_backend()
|
|
51
|
+
)
|
|
52
|
+
pkb = p_key.private_bytes(
|
|
53
|
+
encoding=serialization.Encoding.DER,
|
|
54
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
55
|
+
encryption_algorithm=serialization.NoEncryption()
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
session = Session.builder.configs({
|
|
59
|
+
'account': db_config['account'],
|
|
60
|
+
'user': db_config['user'],
|
|
61
|
+
'private_key': pkb,
|
|
62
|
+
'role': db_config['role'],
|
|
63
|
+
'warehouse': db_config['warehouse'],
|
|
64
|
+
'database': db_config['database'],
|
|
65
|
+
'schema': db_config.get('schema', 'public')
|
|
66
|
+
}).create()
|
|
67
|
+
else:
|
|
68
|
+
# Username/password authentication
|
|
69
|
+
session = Session.builder.configs({
|
|
70
|
+
'account': db_config['account'],
|
|
71
|
+
'user': db_config['user'],
|
|
72
|
+
'password': db_config.get('password'),
|
|
73
|
+
'role': db_config['role'],
|
|
74
|
+
'warehouse': db_config['warehouse'],
|
|
75
|
+
'database': db_config['database'],
|
|
76
|
+
'schema': db_config.get('schema', 'public')
|
|
77
|
+
}).create()
|
|
78
|
+
|
|
79
|
+
return session
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print(f"An error occurred creating the Snowflake session: {e}")
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def mysql_connect(db_name, **kwargs):
|
|
86
|
+
db_config = get_db_config(db_name, **kwargs)
|
|
87
|
+
try:
|
|
88
|
+
connection_string = (
|
|
89
|
+
f"mysql+mysqlconnector://{db_config['user']}:{db_config['password']}"
|
|
90
|
+
f"@{db_config['host']}:{db_config.get('port', 3306)}/{db_config['database']}"
|
|
91
|
+
)
|
|
92
|
+
engine = create_engine(connection_string)
|
|
93
|
+
con = engine.connect()
|
|
94
|
+
return con
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print(f"An error occurred creating the MySQL connection: {e}")
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def read_mysql(db_name, query):
|
|
101
|
+
"""
|
|
102
|
+
Read data from a MySQL database into a Pandas DataFrame.
|
|
103
|
+
:param db_name: Name of the database as defined in the config file.
|
|
104
|
+
:param query: SQL query to execute.
|
|
105
|
+
:return:
|
|
106
|
+
"""
|
|
107
|
+
con = mysql_connect(db_name)
|
|
108
|
+
if con is None:
|
|
109
|
+
return pd.DataFrame()
|
|
110
|
+
try:
|
|
111
|
+
df = pd.read_sql(query, con)
|
|
112
|
+
df.columns = df.columns.str.lower()
|
|
113
|
+
return df
|
|
114
|
+
except Exception as e:
|
|
115
|
+
print(f"An error occurred reading from MySQL: {e}")
|
|
116
|
+
return pd.DataFrame()
|
|
117
|
+
finally:
|
|
118
|
+
con.close()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def execute_mysql(query, dbname):
|
|
122
|
+
"""
|
|
123
|
+
Execute a query on a MySQL database.
|
|
124
|
+
:param query: SQL query to execute.
|
|
125
|
+
:param dbname: Database name as defined in the config file.
|
|
126
|
+
:return:
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
# Establish a connection and run the query
|
|
130
|
+
con = mysql_connect(dbname)
|
|
131
|
+
mycursor = con.connection.cursor()
|
|
132
|
+
mycursor.execute(query)
|
|
133
|
+
except Exception as e:
|
|
134
|
+
# Handle errors
|
|
135
|
+
print('An error was encountered executing mysql, ', e)
|
|
136
|
+
finally:
|
|
137
|
+
con.close()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def execute_snowflake(db_name, query):
|
|
141
|
+
"""
|
|
142
|
+
Execute a query on Snowflake using Snowpark.
|
|
143
|
+
:param query: SQL query to execute.
|
|
144
|
+
:param db_name: Database name as defined in the config file.
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
# Establish session
|
|
148
|
+
session = snowflake_connect(db_name)
|
|
149
|
+
if not session:
|
|
150
|
+
print("Failed to connect to Snowflake.")
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
# Execute query
|
|
154
|
+
session.sql(query).collect()
|
|
155
|
+
print("Query executed successfully.")
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
print(f"An error occurred executing in Snowflake: {e}")
|
|
159
|
+
|
|
160
|
+
finally:
|
|
161
|
+
if session:
|
|
162
|
+
session.close()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def read_snowflake(db_name, query):
|
|
166
|
+
"""
|
|
167
|
+
Fetch data from Snowflake into a Pandas DataFrame using Snowpark.
|
|
168
|
+
:param db_name: Database name as defined in the config file.
|
|
169
|
+
:param query: SQL query to fetch data.
|
|
170
|
+
:return: Pandas DataFrame.
|
|
171
|
+
"""
|
|
172
|
+
try:
|
|
173
|
+
# Establish session
|
|
174
|
+
session = snowflake_connect(db_name)
|
|
175
|
+
if not session:
|
|
176
|
+
print("Failed to connect to Snowflake.")
|
|
177
|
+
return pd.DataFrame()
|
|
178
|
+
|
|
179
|
+
# Execute query and fetch data
|
|
180
|
+
df = session.sql(query).to_pandas()
|
|
181
|
+
df.columns = df.columns.str.lower() # Normalize column names
|
|
182
|
+
return df
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
print(f"An error occurred reading from Snowflake: {e}")
|
|
186
|
+
return pd.DataFrame()
|
|
187
|
+
|
|
188
|
+
finally:
|
|
189
|
+
if session:
|
|
190
|
+
session.close()
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def write_data(db_name, schema, df, table_name, overwrite=True, chunk_size=1000, **kwargs):
|
|
194
|
+
"""
|
|
195
|
+
Write a Pandas DataFrame to a Snowflake table using Snowpark.
|
|
196
|
+
:param db_name: Database name as defined in the config file.
|
|
197
|
+
:param schema: Schema where the table exists.
|
|
198
|
+
:param df: Pandas DataFrame to write.
|
|
199
|
+
:param table_name: Table name to write to.
|
|
200
|
+
:param overwrite: Whether to overwrite the table if it exists.
|
|
201
|
+
"""
|
|
202
|
+
try:
|
|
203
|
+
# Establish session
|
|
204
|
+
session = snowflake_connect(db_name)
|
|
205
|
+
if not session:
|
|
206
|
+
print("Failed to connect to Snowflake.")
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
# Write DataFrame to Snowflake
|
|
210
|
+
session.write_pandas(
|
|
211
|
+
df=df,
|
|
212
|
+
schema=schema,
|
|
213
|
+
table_name=table_name,
|
|
214
|
+
auto_create_table=True,
|
|
215
|
+
overwrite=overwrite,
|
|
216
|
+
quote_identifiers=False,
|
|
217
|
+
chunk_size=chunk_size,
|
|
218
|
+
**kwargs
|
|
219
|
+
)
|
|
220
|
+
print(f"Data written successfully to {schema}.{table_name}.")
|
|
221
|
+
|
|
222
|
+
except Exception as e:
|
|
223
|
+
print(f"An error occurred writing to Snowflake: {e}")
|
|
224
|
+
|
|
225
|
+
finally:
|
|
226
|
+
if session:
|
|
227
|
+
session.close()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def get_data(db_name, query, **kwargs):
|
|
231
|
+
"""
|
|
232
|
+
Get data from a database
|
|
233
|
+
:param db_name: The name of the database as defined in the config file.
|
|
234
|
+
:param query: SQL query to execute.
|
|
235
|
+
:return: Returns a pandas dataframe
|
|
236
|
+
"""
|
|
237
|
+
db_config = get_db_config(db_name, **kwargs)
|
|
238
|
+
# Call the correct database read function
|
|
239
|
+
if db_config['type'] == 'snowflake':
|
|
240
|
+
print(get_wizard())
|
|
241
|
+
df = read_snowflake(db_name, query)
|
|
242
|
+
elif db_config['type'] == 'mysql':
|
|
243
|
+
print(get_wizard())
|
|
244
|
+
df = read_mysql(db_name, query)
|
|
245
|
+
else:
|
|
246
|
+
print('Valid database types are, snowflake and mysql')
|
|
247
|
+
return df
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def run_sql(db_name, query, **kwargs):
|
|
251
|
+
"""
|
|
252
|
+
Run a SQL query on a database
|
|
253
|
+
:param db_name: The name of the database as defined in the config file.
|
|
254
|
+
:param query: SQL query to execute.
|
|
255
|
+
:return: None
|
|
256
|
+
"""
|
|
257
|
+
db_config = get_db_config(db_name, **kwargs)
|
|
258
|
+
# Call the correct database run function
|
|
259
|
+
if db_config['type'] == 'snowflake':
|
|
260
|
+
print(get_wizard())
|
|
261
|
+
execute_snowflake(db_name, query)
|
|
262
|
+
elif db_config['type'] == 'mysql':
|
|
263
|
+
print(get_wizard())
|
|
264
|
+
execute_mysql(db_name, query)
|
|
265
|
+
else:
|
|
266
|
+
print('Valid database types are, snowflake and mysql')
|