Skip to content

Commit db38ea5

Browse files
authored
Optional DB drivers (#15)
* Optional DB drivers
1 parent 7d01dc5 commit db38ea5

File tree

11 files changed

+84
-17
lines changed

11 files changed

+84
-17
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ source venv/bin/activate
1515
Once the virtual environment is activated, you can install cloud2sql:
1616

1717
```bash
18-
pip install cloud2sql
18+
pip install cloud2sql[all]
1919
```
2020

21+
If you only require support for a specific database, instead of `cloud2sql[all]` you can choose between `cloud2sql[snowflake]`, `cloud2sql[parquet]`, `cloud2sql[postgresql]`, `cloud2sql[mysql]`.
22+
2123
## Usage
2224

2325
The sources and destinations for `cloud2sql` are configured via a configuration file. Create your own configuration by adjusting the [config template file](./config-template.yaml).

cloud2sql/__main__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from cloud2sql.analytics import PosthogEventSender, NoEventSender, AnalyticsEventSender
99
from cloud2sql.collect_plugins import collect_from_plugins, configure
10-
from cloud2sql.util import db_string_from_config
10+
from cloud2sql.util import db_string_from_config, check_parquet_driver
1111

1212
# Will fail in case snowflake is not installed - which is fine.
1313
try:
@@ -58,8 +58,11 @@ def main() -> None:
5858
setup_logger("resoto.cloud2sql", level=args.log_level, force=True)
5959
sender = NoEventSender() if args.analytics_opt_out else PosthogEventSender()
6060
config = configure(args.config)
61-
is_parquet = next(iter(config["destinations"].keys()), None) == "parquet"
62-
engine = None if is_parquet else create_engine(db_string_from_config(config))
61+
engine = None
62+
if next(iter(config["destinations"].keys()), None) == "parquet":
63+
check_parquet_driver()
64+
else:
65+
engine = create_engine(db_string_from_config(config))
6366
collect(engine, args, sender)
6467
except Exception as e:
6568
if args.debug: # raise exception and show complete tracelog

cloud2sql/collect_plugins.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@
3131
from cloud2sql.analytics import AnalyticsEventSender
3232
from cloud2sql.show_progress import CollectInfo
3333
from cloud2sql.sql import SqlUpdater, sql_updater
34-
from cloud2sql.parquet import ParquetModel, ParquetWriter
34+
35+
try:
36+
from cloud2sql.parquet import ParquetModel, ParquetWriter
37+
except ImportError:
38+
pass
3539

3640

3741
log = getLogger("resoto.cloud2sql")

cloud2sql/util.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Union, List, Optional, Any
22

33
from resotolib.types import JsonElement, Json
4+
from sqlalchemy.engine import create_engine
45

56

67
def value_in_path(element: JsonElement, path_or_name: Union[List[str], str]) -> Optional[Any]:
@@ -26,6 +27,7 @@ def db_string_from_config(config: Json) -> str:
2627

2728
db_type = list(destinations.keys())[0]
2829
db_config = destinations[db_type]
30+
db_type = update_db_type(db_type)
2931
user = db_config.get("user")
3032
password = db_config.get("password")
3133
host = db_config.get("host")
@@ -52,4 +54,44 @@ def db_string_from_config(config: Json) -> str:
5254
if len(args) > 0:
5355
db_uri += "?" + "&".join([f"{k}={v}" for k, v in args.items()])
5456

57+
check_db_driver(db_uri)
58+
5559
return db_uri
60+
61+
62+
def update_db_type(db_type: str) -> str:
63+
if db_type == "mysql":
64+
db_type = "mysql+pymysql"
65+
elif db_type == "mariadb":
66+
db_type = "mariadb+pymysql"
67+
return db_type
68+
69+
70+
def check_db_driver(db_uri: str) -> None:
71+
try:
72+
create_engine(db_uri)
73+
except ModuleNotFoundError:
74+
err = "The database type you configured is not installed. "
75+
if db_uri.startswith("mysql") or db_uri.startswith("mariadb"):
76+
err += "Please run 'pip install cloud2sql[mysql]' and try again."
77+
elif db_uri.startswith("postgresql"):
78+
err += "Please run 'pip install cloud2sql[postgresql]' and try again."
79+
elif db_uri.startswith("snowflake"):
80+
err += "Please run 'pip install cloud2sql[snowflake]' and try again."
81+
elif db_uri.startswith("mssql"):
82+
err += "Please install the pymssql package and try again."
83+
elif db_uri.startswith("oracle"):
84+
err += "Please install the cx_oracle package and try again."
85+
else:
86+
err += "Please install the required dependencies and try again."
87+
raise ModuleNotFoundError(err)
88+
89+
90+
def check_parquet_driver() -> None:
91+
try:
92+
import pyarrow # noqa: F401
93+
except ModuleNotFoundError:
94+
raise ModuleNotFoundError(
95+
"The parquet format you configured is not installed. "
96+
"Please run 'pip install cloud2sql[parquet]' and try again."
97+
)

requirements-mysql.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pymysql>=1.0.2

requirements-parquet.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pyarrow==10.0.1

requirements-postgresql.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
psycopg2-binary>=2.9.5

requirements-snowflake.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
snowflake-sqlalchemy>=1.4.5

requirements.txt

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,6 @@ resotoclient>=1.2.1
88
posthog>=2.2.0
99
requests>=2.28.1
1010

11-
# parquet support
12-
pyarrow==10.0.1
13-
14-
# bundle popular db drivers
15-
psycopg2-binary>=2.9.5 # postgres
16-
pymysql>=1.0.2 # mysql + mariadb
17-
18-
# Install snowflake connector
19-
# Note: this version does not support Python 3.11
20-
# See: https://github.com/snowflakedb/snowflake-connector-python/pull/1349 for reference
21-
snowflake-sqlalchemy>=1.4.5
22-
2311
resotolib>=3.0.0
2412
# all collector plugins
2513
resoto-plugin-aws>=3.0.0

setup.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@
99
with open("requirements.txt") as f:
1010
required = f.read().splitlines()
1111

12+
with open("requirements-mysql.txt") as f:
13+
required_mysql = f.read().splitlines()
14+
15+
with open("requirements-postgresql.txt") as f:
16+
required_postgresql = f.read().splitlines()
17+
18+
with open("requirements-parquet.txt") as f:
19+
required_parquet = f.read().splitlines()
20+
21+
with open("requirements-snowflake.txt") as f:
22+
required_snowflake = f.read().splitlines()
23+
1224
with open("requirements-test.txt") as f:
1325
test_required = f.read().splitlines()
1426

@@ -28,6 +40,14 @@
2840
classifiers=["Programming Language :: Python :: 3"],
2941
entry_points={"console_scripts": ["cloud2sql=cloud2sql.__main__:main"]},
3042
install_requires=required,
43+
extras_require={
44+
"all": required_mysql + required_postgresql + required_snowflake + required_parquet,
45+
"mysql": required_mysql,
46+
"mariadb": required_mysql,
47+
"postgresql": required_postgresql,
48+
"snowflake": required_snowflake,
49+
"parquet": required_parquet,
50+
},
3151
license="Apache Software License 2.0",
3252
long_description=readme,
3353
long_description_content_type="text/markdown",

0 commit comments

Comments
 (0)