rgwfuncs 0.0.112__tar.gz → 0.0.115__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.
- {rgwfuncs-0.0.112/src/rgwfuncs.egg-info → rgwfuncs-0.0.115}/PKG-INFO +2 -3
- {rgwfuncs-0.0.112 → rgwfuncs-0.0.115}/pyproject.toml +3 -2
- rgwfuncs-0.0.115/setup.cfg +4 -0
- {rgwfuncs-0.0.112 → rgwfuncs-0.0.115}/src/rgwfuncs/__init__.py +1 -1
- {rgwfuncs-0.0.112 → rgwfuncs-0.0.115}/src/rgwfuncs/df_lib.py +126 -0
- {rgwfuncs-0.0.112 → rgwfuncs-0.0.115}/src/rgwfuncs/str_lib.py +35 -20
- {rgwfuncs-0.0.112 → rgwfuncs-0.0.115/src/rgwfuncs.egg-info}/PKG-INFO +2 -3
- {rgwfuncs-0.0.112 → rgwfuncs-0.0.115}/src/rgwfuncs.egg-info/SOURCES.txt +0 -2
- {rgwfuncs-0.0.112 → rgwfuncs-0.0.115}/src/rgwfuncs.egg-info/requires.txt +1 -0
- rgwfuncs-0.0.112/setup.cfg +0 -44
- rgwfuncs-0.0.112/src/rgwfuncs.egg-info/entry_points.txt +0 -2
- {rgwfuncs-0.0.112 → rgwfuncs-0.0.115}/LICENSE +0 -0
- {rgwfuncs-0.0.112 → rgwfuncs-0.0.115}/README.md +0 -0
- {rgwfuncs-0.0.112 → rgwfuncs-0.0.115}/src/rgwfuncs/docs_lib.py +0 -0
- {rgwfuncs-0.0.112 → rgwfuncs-0.0.115}/src/rgwfuncs/interactive_shell_lib.py +0 -0
- {rgwfuncs-0.0.112 → rgwfuncs-0.0.115}/src/rgwfuncs.egg-info/dependency_links.txt +0 -0
- {rgwfuncs-0.0.112 → rgwfuncs-0.0.115}/src/rgwfuncs.egg-info/top_level.txt +0 -0
@@ -1,9 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: rgwfuncs
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.115
|
4
4
|
Summary: A functional programming paradigm for mathematical modelling and data science
|
5
|
-
Home-page: https://github.com/ryangerardwilson/rgwfuncs
|
6
|
-
Author: Ryan Gerard Wilson
|
7
5
|
Author-email: Ryan Gerard Wilson <ryangerardwilson@gmail.com>
|
8
6
|
Project-URL: Homepage, https://github.com/ryangerardwilson/rgwfuncs
|
9
7
|
Project-URL: Issues, https://github.com/ryangerardwilson/rgwfuncs
|
@@ -25,6 +23,7 @@ Requires-Dist: slack-sdk
|
|
25
23
|
Requires-Dist: google-api-python-client
|
26
24
|
Requires-Dist: boto3
|
27
25
|
Requires-Dist: pyfiglet
|
26
|
+
Requires-Dist: snowflake-connector-python
|
28
27
|
Dynamic: license-file
|
29
28
|
|
30
29
|
# RGWFUNCS
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "rgwfuncs"
|
7
|
-
version = "0.0.
|
7
|
+
version = "0.0.115"
|
8
8
|
authors = [
|
9
9
|
{ name = "Ryan Gerard Wilson", email = "ryangerardwilson@gmail.com" },
|
10
10
|
]
|
@@ -28,7 +28,8 @@ dependencies = [
|
|
28
28
|
"slack-sdk",
|
29
29
|
"google-api-python-client",
|
30
30
|
"boto3",
|
31
|
-
"pyfiglet"
|
31
|
+
"pyfiglet",
|
32
|
+
"snowflake-connector-python"
|
32
33
|
]
|
33
34
|
|
34
35
|
dynamic = ["scripts"]
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# This file is automatically generated
|
2
2
|
# Dynamically importing functions from modules
|
3
3
|
|
4
|
-
from .df_lib import append_columns, append_percentile_classification_column, append_ranged_classification_column, append_ranged_date_classification_column, append_rows, append_xgb_labels, append_xgb_logistic_regression_predictions, append_xgb_regression_predictions, bag_union_join, bottom_n_unique_values, cascade_sort, delete_rows, drop_duplicates, drop_duplicates_retain_first, drop_duplicates_retain_last, filter_dataframe, filter_indian_mobiles, first_n_rows, from_raw_data, insert_dataframe_in_sqlite_database, last_n_rows, left_join, limit_dataframe, load_data_from_aws_athena_query, load_data_from_big_query, load_data_from_path, load_data_from_query, load_data_from_sqlite_path, load_fresh_data_or_pull_from_cache, mask_against_dataframe, mask_against_dataframe_converse, numeric_clean, order_columns, print_correlation, print_dataframe, print_memory_usage, print_n_frequency_cascading, print_n_frequency_linear, rename_columns, retain_columns, right_join, send_data_to_email, send_data_to_slack, send_dataframe_via_telegram, sync_dataframe_to_sqlite_database, top_n_unique_values, union_join, update_rows
|
4
|
+
from .df_lib import append_columns, append_percentile_classification_column, append_ranged_classification_column, append_ranged_date_classification_column, append_rows, append_xgb_labels, append_xgb_logistic_regression_predictions, append_xgb_regression_predictions, bag_union_join, bottom_n_unique_values, cascade_sort, delete_rows, drop_duplicates, drop_duplicates_retain_first, drop_duplicates_retain_last, filter_dataframe, filter_indian_mobiles, first_n_rows, from_raw_data, insert_dataframe_in_sqlite_database, last_n_rows, left_join, limit_dataframe, load_data_from_aws_athena_query, load_data_from_big_query, load_data_from_path, load_data_from_query, load_data_from_snowflake, load_data_from_sqlite_path, load_fresh_data_or_pull_from_cache, mask_against_dataframe, mask_against_dataframe_converse, numeric_clean, order_columns, print_correlation, print_dataframe, print_memory_usage, print_n_frequency_cascading, print_n_frequency_linear, rename_columns, retain_columns, right_join, send_data_to_email, send_data_to_slack, send_dataframe_via_telegram, sync_dataframe_to_sqlite_database, top_n_unique_values, union_join, update_rows
|
5
5
|
from .interactive_shell_lib import interactive_shell
|
6
6
|
from .docs_lib import docs
|
7
7
|
from .str_lib import heading, send_telegram_message, sub_heading, title
|
@@ -20,6 +20,9 @@ from email.mime.text import MIMEText
|
|
20
20
|
from email.mime.base import MIMEBase
|
21
21
|
from email import encoders
|
22
22
|
from googleapiclient.discovery import build
|
23
|
+
import snowflake.connector
|
24
|
+
from cryptography.hazmat.primitives import serialization
|
25
|
+
from cryptography.hazmat.backends import default_backend
|
23
26
|
import base64
|
24
27
|
import boto3
|
25
28
|
from typing import Optional, Dict, List, Tuple, Any, Callable, Union
|
@@ -563,6 +566,129 @@ def load_data_from_big_query(
|
|
563
566
|
return pd.DataFrame(rows, columns=columns)
|
564
567
|
|
565
568
|
|
569
|
+
def load_data_from_snowflake(
|
570
|
+
query: str,
|
571
|
+
private_key_path: Optional[str] = None,
|
572
|
+
private_key_password: Optional[str] = None,
|
573
|
+
account: Optional[str] = None,
|
574
|
+
user: Optional[str] = None,
|
575
|
+
warehouse: Optional[str] = None,
|
576
|
+
database: Optional[str] = None,
|
577
|
+
schema: Optional[str] = None,
|
578
|
+
preset: Optional[str] = None
|
579
|
+
) -> pd.DataFrame:
|
580
|
+
"""
|
581
|
+
Load data from Snowflake with a query, returning a DataFrame.
|
582
|
+
|
583
|
+
Parameters:
|
584
|
+
query (str): The SQL query to execute.
|
585
|
+
private_key_path (Optional[str]): Path to the private key file (PEM).
|
586
|
+
private_key_password (Optional[str]): Password for the encrypted private key (if any).
|
587
|
+
account (Optional[str]): Snowflake account identifier.
|
588
|
+
user (Optional[str]): Snowflake username.
|
589
|
+
warehouse (Optional[str]): Snowflake warehouse name.
|
590
|
+
database (Optional[str]): Snowflake database name.
|
591
|
+
schema (Optional[str]): Snowflake schema name.
|
592
|
+
preset (Optional[str]): Name of the Snowflake preset in the .rgwfuncsrc file.
|
593
|
+
|
594
|
+
Returns:
|
595
|
+
pd.DataFrame: DataFrame with the query results.
|
596
|
+
|
597
|
+
Raises:
|
598
|
+
ValueError: If both preset and direct creds are mixed, neither provided,
|
599
|
+
or required creds missing.
|
600
|
+
FileNotFoundError: If no '.rgwfuncsrc' found for preset.
|
601
|
+
RuntimeError: If preset not found or missing details.
|
602
|
+
"""
|
603
|
+
def get_config() -> dict:
|
604
|
+
"""Hunt for '.rgwfuncsrc' upwards like a sane person."""
|
605
|
+
current_dir = os.getcwd()
|
606
|
+
while True:
|
607
|
+
config_path = os.path.join(current_dir, '.rgwfuncsrc') # Wait, your example had .rgwfuncsrc, assuming that's not a typo.
|
608
|
+
if os.path.isfile(config_path):
|
609
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
610
|
+
content = f.read().strip()
|
611
|
+
if not content:
|
612
|
+
raise ValueError(f"Empty config at {config_path}'")
|
613
|
+
try:
|
614
|
+
return json.loads(content)
|
615
|
+
except json.JSONDecodeError as e:
|
616
|
+
raise ValueError(f"JSON crap in {config_path}: {e}")
|
617
|
+
parent = os.path.dirname(current_dir)
|
618
|
+
if parent == current_dir:
|
619
|
+
raise FileNotFoundError("Can't find '.rgwfuncsrc' anywhere useful")
|
620
|
+
current_dir = parent
|
621
|
+
|
622
|
+
def get_db_preset(config: dict, preset_name: str) -> dict:
|
623
|
+
"""Grab the preset or complain."""
|
624
|
+
db_presets = config.get('db_presets', [])
|
625
|
+
for p in db_presets:
|
626
|
+
if p.get('name') == preset_name:
|
627
|
+
return p
|
628
|
+
raise RuntimeError(f"Preset '{preset_name}' missing, go add it")
|
629
|
+
|
630
|
+
# Input validation—because people are idiots and will screw this up
|
631
|
+
direct_creds = [private_key_path, private_key_password, account, user, warehouse, database, schema]
|
632
|
+
has_direct = any(d is not None for d in direct_creds)
|
633
|
+
if preset and has_direct:
|
634
|
+
raise ValueError("Don't mix preset with direct params, pick one")
|
635
|
+
if not preset and not has_direct_creds: # Wait, better: require at least the core ones
|
636
|
+
if not private_key_path or not account or not user:
|
637
|
+
raise ValueError("Need private_key_path, account, and user if no preset")
|
638
|
+
if not preset and not preset:
|
639
|
+
raise ValueError("Provide preset or direct creds")
|
640
|
+
|
641
|
+
# Load creds from preset
|
642
|
+
if preset:
|
643
|
+
config = get_config()
|
644
|
+
creds = get_db_preset(config, preset)
|
645
|
+
if creds.get('db_type') != 'snowflake':
|
646
|
+
raise ValueError(f"Preset '{preset}' isn't for Snowflake")
|
647
|
+
private_key_path = creds.get('private_key_path')
|
648
|
+
private_key_password = creds.get('private_key_password')
|
649
|
+
account = creds.get('account')
|
650
|
+
user = creds.get('user')
|
651
|
+
warehouse = creds.get('warehouse')
|
652
|
+
database = creds.get('database')
|
653
|
+
schema = creds.get('schema')
|
654
|
+
if not private_key_path or not account or not user:
|
655
|
+
raise ValueError(f"Missing key creds in preset '{preset}'")
|
656
|
+
|
657
|
+
# Load the damn private key
|
658
|
+
if not os.path.exists(private_key_path):
|
659
|
+
raise FileNotFoundError(f"Private key missing at {private_key_path}")
|
660
|
+
with open(private_key_path, "rb") as key_file:
|
661
|
+
pkey_data = key_file.read()
|
662
|
+
private_key = serialization.load_pem_private_key(
|
663
|
+
pkey_data,
|
664
|
+
password=private_key_password.encode() if private_key_password else None,
|
665
|
+
backend=default_backend()
|
666
|
+
)
|
667
|
+
|
668
|
+
# Connect and query—keep it tight, no bloat
|
669
|
+
try:
|
670
|
+
conn = {
|
671
|
+
'account': account,
|
672
|
+
'user': user,
|
673
|
+
'private_key': private_key,
|
674
|
+
}
|
675
|
+
if warehouse:
|
676
|
+
conn['warehouse'] = warehouse
|
677
|
+
if database:
|
678
|
+
conn['database'] = database
|
679
|
+
if schema:
|
680
|
+
conn['schema'] = schema
|
681
|
+
conn = snowflake.connector.connect(**conn)
|
682
|
+
cur = conn.cursor()
|
683
|
+
results = cur.execute(query).fetchall()
|
684
|
+
columns = [desc[0] for desc in cur.description]
|
685
|
+
cur.close()
|
686
|
+
conn.close()
|
687
|
+
return pd.DataFrame(results, columns=columns)
|
688
|
+
except Exception as e:
|
689
|
+
raise RuntimeError(f"Query bombed: {e}")
|
690
|
+
|
691
|
+
|
566
692
|
def load_data_from_aws_athena_query(
|
567
693
|
query: str,
|
568
694
|
aws_region: Optional[str] = None,
|
@@ -101,17 +101,19 @@ def send_telegram_message(message: str, preset_name: Optional[str] = None, bot_t
|
|
101
101
|
response.raise_for_status()
|
102
102
|
|
103
103
|
|
104
|
-
def title(text: str, font: str = "slant", typing_speed: float = 0.005) -> None:
|
104
|
+
def title(text: str, font: str = "slant", typing_speed: float = 0.005, animate: bool = True) -> None:
|
105
105
|
"""
|
106
|
-
Print text as ASCII art with
|
106
|
+
Print text as ASCII art with an optional typewriter effect using the specified font (default: slant),
|
107
107
|
indented by 4 spaces. All output, including errors and separators, is printed with the
|
108
|
-
typewriter effect
|
108
|
+
typewriter effect if animate is True.
|
109
109
|
|
110
110
|
Args:
|
111
111
|
text (str): The text to convert to ASCII art.
|
112
112
|
font (str, optional): The pyfiglet font to use. Defaults to "slant".
|
113
113
|
typing_speed (float, optional): Delay between printing each character in seconds.
|
114
114
|
Defaults to 0.005.
|
115
|
+
animate (bool, optional): If True, applies typewriter effect. If False, prints instantly.
|
116
|
+
Defaults to True.
|
115
117
|
|
116
118
|
Raises:
|
117
119
|
ValueError: If the specified font is invalid or unavailable.
|
@@ -129,20 +131,26 @@ def title(text: str, font: str = "slant", typing_speed: float = 0.005) -> None:
|
|
129
131
|
# Indent each line by 4 spaces
|
130
132
|
indented_ascii_art = '\n'.join(' ' + line for line in ascii_art.splitlines())
|
131
133
|
|
132
|
-
# Print ASCII art with typewriter effect
|
134
|
+
# Print ASCII art with or without typewriter effect
|
133
135
|
print(heading_color, end='')
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
136
|
+
if animate:
|
137
|
+
for char in indented_ascii_art + '\n':
|
138
|
+
print(char, end='', flush=True)
|
139
|
+
if char != '\n': # Don't delay on newlines
|
140
|
+
time.sleep(typing_speed)
|
141
|
+
else:
|
142
|
+
print(indented_ascii_art + '\n', end='', flush=True)
|
138
143
|
print(reset_color, end='')
|
139
144
|
|
140
|
-
# Print separator line with typewriter effect
|
145
|
+
# Print separator line with or without typewriter effect
|
141
146
|
print(heading_color, end='')
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
147
|
+
if animate:
|
148
|
+
for char in '=' * 79 + '\n':
|
149
|
+
print(char, end='', flush=True)
|
150
|
+
if char != '\n':
|
151
|
+
time.sleep(typing_speed)
|
152
|
+
else:
|
153
|
+
print('=' * 79 + '\n', end='', flush=True)
|
146
154
|
print(reset_color, end='')
|
147
155
|
|
148
156
|
except Exception as e:
|
@@ -150,19 +158,26 @@ def title(text: str, font: str = "slant", typing_speed: float = 0.005) -> None:
|
|
150
158
|
if "font" in str(e).lower():
|
151
159
|
error_msg = f"Invalid or unavailable font: {font}. Ensure the font is supported by pyfiglet.\n"
|
152
160
|
print(reset_color, end='')
|
161
|
+
if animate:
|
162
|
+
for char in error_msg:
|
163
|
+
print(char, end='', flush=True)
|
164
|
+
if char != '\n':
|
165
|
+
time.sleep(typing_speed)
|
166
|
+
else:
|
167
|
+
print(error_msg, end='', flush=True)
|
168
|
+
raise ValueError(error_msg)
|
169
|
+
error_msg = f"Error generating ASCII art for \"{text}\" with font {font}: {e}\n"
|
170
|
+
print(reset_color, end='')
|
171
|
+
if animate:
|
153
172
|
for char in error_msg:
|
154
173
|
print(char, end='', flush=True)
|
155
174
|
if char != '\n':
|
156
175
|
time.sleep(typing_speed)
|
157
|
-
|
158
|
-
|
159
|
-
print(reset_color, end='')
|
160
|
-
for char in error_msg:
|
161
|
-
print(char, end='', flush=True)
|
162
|
-
if char != '\n':
|
163
|
-
time.sleep(typing_speed)
|
176
|
+
else:
|
177
|
+
print(error_msg, end='', flush=True)
|
164
178
|
raise RuntimeError(error_msg)
|
165
179
|
|
180
|
+
|
166
181
|
def heading(text: str, typing_speed: float = 0.002) -> None:
|
167
182
|
"""
|
168
183
|
Print a heading with the specified text in uppercase,
|
@@ -1,9 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: rgwfuncs
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.115
|
4
4
|
Summary: A functional programming paradigm for mathematical modelling and data science
|
5
|
-
Home-page: https://github.com/ryangerardwilson/rgwfuncs
|
6
|
-
Author: Ryan Gerard Wilson
|
7
5
|
Author-email: Ryan Gerard Wilson <ryangerardwilson@gmail.com>
|
8
6
|
Project-URL: Homepage, https://github.com/ryangerardwilson/rgwfuncs
|
9
7
|
Project-URL: Issues, https://github.com/ryangerardwilson/rgwfuncs
|
@@ -25,6 +23,7 @@ Requires-Dist: slack-sdk
|
|
25
23
|
Requires-Dist: google-api-python-client
|
26
24
|
Requires-Dist: boto3
|
27
25
|
Requires-Dist: pyfiglet
|
26
|
+
Requires-Dist: snowflake-connector-python
|
28
27
|
Dynamic: license-file
|
29
28
|
|
30
29
|
# RGWFUNCS
|
@@ -1,7 +1,6 @@
|
|
1
1
|
LICENSE
|
2
2
|
README.md
|
3
3
|
pyproject.toml
|
4
|
-
setup.cfg
|
5
4
|
src/rgwfuncs/__init__.py
|
6
5
|
src/rgwfuncs/df_lib.py
|
7
6
|
src/rgwfuncs/docs_lib.py
|
@@ -10,6 +9,5 @@ src/rgwfuncs/str_lib.py
|
|
10
9
|
src/rgwfuncs.egg-info/PKG-INFO
|
11
10
|
src/rgwfuncs.egg-info/SOURCES.txt
|
12
11
|
src/rgwfuncs.egg-info/dependency_links.txt
|
13
|
-
src/rgwfuncs.egg-info/entry_points.txt
|
14
12
|
src/rgwfuncs.egg-info/requires.txt
|
15
13
|
src/rgwfuncs.egg-info/top_level.txt
|
rgwfuncs-0.0.112/setup.cfg
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
[metadata]
|
2
|
-
name = rgwfuncs
|
3
|
-
version = 0.0.112
|
4
|
-
author = Ryan Gerard Wilson
|
5
|
-
author_email = ryangerardwilson@gmail.com
|
6
|
-
description = A functional programming paradigm for mathematical modelling and data science
|
7
|
-
long_description = file: README.md
|
8
|
-
long_description_content_type = text/markdown
|
9
|
-
url = https://github.com/ryangerardwilson/rgwfuncs
|
10
|
-
classifiers =
|
11
|
-
Programming Language :: Python :: 3
|
12
|
-
License :: OSI Approved :: MIT License
|
13
|
-
Operating System :: OS Independent
|
14
|
-
|
15
|
-
[options]
|
16
|
-
packages = find:
|
17
|
-
package_dir =
|
18
|
-
= src
|
19
|
-
python_requires = >=3.10
|
20
|
-
install_requires =
|
21
|
-
pandas
|
22
|
-
pymssql
|
23
|
-
mysql-connector-python
|
24
|
-
clickhouse-connect
|
25
|
-
google-cloud-bigquery
|
26
|
-
google-auth
|
27
|
-
xgboost
|
28
|
-
requests
|
29
|
-
slack-sdk
|
30
|
-
google-api-python-client
|
31
|
-
boto3
|
32
|
-
pyfiglet
|
33
|
-
|
34
|
-
[options.packages.find]
|
35
|
-
where = src
|
36
|
-
|
37
|
-
[options.entry_points]
|
38
|
-
console_scripts =
|
39
|
-
rgwfuncs = rgwfuncs:main
|
40
|
-
|
41
|
-
[egg_info]
|
42
|
-
tag_build =
|
43
|
-
tag_date = 0
|
44
|
-
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|