dataops-testgen 2.2.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.
- dataops_testgen-2.2.0.dist-info/LICENSE +203 -0
- dataops_testgen-2.2.0.dist-info/METADATA +287 -0
- dataops_testgen-2.2.0.dist-info/NOTICE +5 -0
- dataops_testgen-2.2.0.dist-info/RECORD +270 -0
- dataops_testgen-2.2.0.dist-info/WHEEL +5 -0
- dataops_testgen-2.2.0.dist-info/entry_points.txt +2 -0
- dataops_testgen-2.2.0.dist-info/top_level.txt +1 -0
- testgen/__init__.py +0 -0
- testgen/__main__.py +770 -0
- testgen/commands/__init__.py +0 -0
- testgen/commands/queries/__init__.py +0 -0
- testgen/commands/queries/execute_cat_tests_query.py +95 -0
- testgen/commands/queries/execute_tests_query.py +160 -0
- testgen/commands/queries/generate_tests_query.py +94 -0
- testgen/commands/queries/profiling_query.py +366 -0
- testgen/commands/queries/test_parameter_validation_query.py +88 -0
- testgen/commands/run_execute_cat_tests.py +162 -0
- testgen/commands/run_execute_tests.py +168 -0
- testgen/commands/run_generate_tests.py +107 -0
- testgen/commands/run_get_entities.py +122 -0
- testgen/commands/run_launch_db_config.py +84 -0
- testgen/commands/run_observability_exporter.py +330 -0
- testgen/commands/run_profiling_bridge.py +495 -0
- testgen/commands/run_quick_start.py +168 -0
- testgen/commands/run_setup_profiling_tools.py +96 -0
- testgen/commands/run_test_definition.py +146 -0
- testgen/commands/run_test_parameter_validation.py +135 -0
- testgen/commands/run_upgrade_db_config.py +156 -0
- testgen/common/__init__.py +8 -0
- testgen/common/clean_sql.py +53 -0
- testgen/common/credentials.py +25 -0
- testgen/common/database/__init__.py +0 -0
- testgen/common/database/database_service.py +629 -0
- testgen/common/database/flavor/__init__.py +0 -0
- testgen/common/database/flavor/flavor_service.py +75 -0
- testgen/common/database/flavor/mssql_flavor_service.py +34 -0
- testgen/common/database/flavor/postgresql_flavor_service.py +5 -0
- testgen/common/database/flavor/redshift_flavor_service.py +22 -0
- testgen/common/database/flavor/snowflake_flavor_service.py +69 -0
- testgen/common/database/flavor/trino_flavor_service.py +21 -0
- testgen/common/date_service.py +68 -0
- testgen/common/display_service.py +85 -0
- testgen/common/docker_service.py +76 -0
- testgen/common/encrypt.py +55 -0
- testgen/common/get_pipeline_parms.py +57 -0
- testgen/common/logs.py +79 -0
- testgen/common/process_service.py +62 -0
- testgen/common/read_file.py +69 -0
- testgen/settings.py +440 -0
- testgen/template/dbsetup/010_create_base_schema.sql +2 -0
- testgen/template/dbsetup/020_create_standard_functions_sprocs.sql +179 -0
- testgen/template/dbsetup/030_initialize_new_schema_structure.sql +735 -0
- testgen/template/dbsetup/040_populate_new_schema_project.sql +59 -0
- testgen/template/dbsetup/050_populate_new_schema_metadata.sql +1517 -0
- testgen/template/dbsetup/060_create_standard_views.sql +248 -0
- testgen/template/dbsetup/070_create_default_users.sql +17 -0
- testgen/template/dbsetup/075_grant_role_rights.sql +43 -0
- testgen/template/dbsetup/080_set_current_revision.sql +5 -0
- testgen/template/dbupgrade/0100_incremental_upgrade.sql +5 -0
- testgen/template/dbupgrade/0101_incremental_upgrade.sql +15 -0
- testgen/template/dbupgrade/0102_incremental_upgrade.sql +4 -0
- testgen/template/dbupgrade/0103_incremental_upgrade.sql +22 -0
- testgen/template/dbupgrade/0104_incremental_upgrade.sql +44 -0
- testgen/template/dbupgrade/0105_incremental_upgrade.sql +1 -0
- testgen/template/dbupgrade/0106_incremental_upgrade.sql +5 -0
- testgen/template/dbupgrade/0107_incremental_upgrade.sql +3 -0
- testgen/template/dbupgrade_helpers/get_tg_revision.sql +2 -0
- testgen/template/exec_cat_tests/ex_cat_build_agg_table_tests.sql +116 -0
- testgen/template/exec_cat_tests/ex_cat_get_distinct_tables.sql +11 -0
- testgen/template/exec_cat_tests/ex_cat_results_parse.sql +69 -0
- testgen/template/exec_cat_tests/ex_cat_retrieve_agg_test_parms.sql +6 -0
- testgen/template/exec_cat_tests/ex_cat_test_query.sql +8 -0
- testgen/template/execution/ex_finalize_test_run_results.sql +37 -0
- testgen/template/execution/ex_get_tests_non_cat.sql +47 -0
- testgen/template/execution/ex_update_test_record_in_testrun_table.sql +27 -0
- testgen/template/execution/ex_write_test_record_to_testrun_table.sql +6 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_no_drops_generic.sql +48 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_num_incr_generic.sql +34 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_percent_above_generic.sql +49 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_percent_within_generic.sql +49 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_same_generic.sql +49 -0
- testgen/template/flavors/generic/exec_query_tests/ex_custom_query_generic.sql +39 -0
- testgen/template/flavors/generic/exec_query_tests/ex_data_match_2way_generic.sql +58 -0
- testgen/template/flavors/generic/exec_query_tests/ex_data_match_generic.sql +44 -0
- testgen/template/flavors/generic/exec_query_tests/ex_prior_match_generic.sql +37 -0
- testgen/template/flavors/generic/exec_query_tests/ex_relative_entropy_generic.sql +53 -0
- testgen/template/flavors/generic/exec_query_tests/ex_window_match_no_drops_generic.sql +46 -0
- testgen/template/flavors/generic/exec_query_tests/ex_window_match_same_generic.sql +59 -0
- testgen/template/flavors/generic/profiling/contingency_counts.sql +3 -0
- testgen/template/flavors/generic/validate_tests/ex_get_project_column_list_generic.sql +3 -0
- testgen/template/flavors/mssql/exec_query_tests/ex_relative_entropy_mssql.sql +53 -0
- testgen/template/flavors/mssql/profiling/project_ddf_query_mssql.sql +35 -0
- testgen/template/flavors/mssql/profiling/project_profiling_query_mssql.yaml +246 -0
- testgen/template/flavors/mssql/profiling/project_secondary_profiling_query_mssql.sql +36 -0
- testgen/template/flavors/mssql/setup_profiling_tools/00_drop_existing_functions_mssql.sql +8 -0
- testgen/template/flavors/mssql/setup_profiling_tools/01_create_functions_mssql.sql +12 -0
- testgen/template/flavors/mssql/setup_profiling_tools/02_create_functions_mssql.sql +54 -0
- testgen/template/flavors/mssql/setup_profiling_tools/create_qc_schema_mssql.sql +4 -0
- testgen/template/flavors/mssql/setup_profiling_tools/grant_execute_privileges_mssql.sql +1 -0
- testgen/template/flavors/postgresql/exec_query_tests/ex_window_match_no_drops_postgresql.sql +46 -0
- testgen/template/flavors/postgresql/exec_query_tests/ex_window_match_same_postgresql.sql +59 -0
- testgen/template/flavors/postgresql/profiling/project_ddf_query_postgresql.sql +42 -0
- testgen/template/flavors/postgresql/profiling/project_profiling_query_postgresql.yaml +225 -0
- testgen/template/flavors/postgresql/profiling/project_secondary_profiling_query_postgresql.sql +28 -0
- testgen/template/flavors/postgresql/setup_profiling_tools/create_functions_postgresql.sql +157 -0
- testgen/template/flavors/postgresql/setup_profiling_tools/create_qc_schema_postgresql.sql +1 -0
- testgen/template/flavors/postgresql/setup_profiling_tools/grant_execute_privileges_postgresql.sql +2 -0
- testgen/template/flavors/redshift/profiling/project_ddf_query_redshift.sql +38 -0
- testgen/template/flavors/redshift/profiling/project_profiling_query_redshift.yaml +221 -0
- testgen/template/flavors/redshift/profiling/project_secondary_profiling_query_redshift.sql +29 -0
- testgen/template/flavors/redshift/setup_profiling_tools/create_functions_redshift.sql +115 -0
- testgen/template/flavors/redshift/setup_profiling_tools/create_qc_schema_redshift.sql +1 -0
- testgen/template/flavors/redshift/setup_profiling_tools/grant_execute_privileges_redshift.sql +2 -0
- testgen/template/flavors/snowflake/profiling/project_ddf_query_snowflake.sql +38 -0
- testgen/template/flavors/snowflake/profiling/project_profiling_query_snowflake.yaml +220 -0
- testgen/template/flavors/snowflake/profiling/project_secondary_profiling_query_snowflake.sql +29 -0
- testgen/template/flavors/snowflake/setup_profiling_tools/create_functions_snowflake.sql +69 -0
- testgen/template/flavors/snowflake/setup_profiling_tools/create_qc_schema_snowflake.sql +1 -0
- testgen/template/flavors/snowflake/setup_profiling_tools/grant_execute_privileges_snowflake.sql +6 -0
- testgen/template/flavors/trino/profiling/project_profiling_query_trino.yaml +219 -0
- testgen/template/flavors/trino/setup_profiling_tools/create_functions_trino.sql +92 -0
- testgen/template/flavors/trino/setup_profiling_tools/create_qc_schema_trino.sql +1 -0
- testgen/template/gen_funny_cat_tests/gen_test_constant.sql +104 -0
- testgen/template/gen_funny_cat_tests/gen_test_distinct_value_ct.sql +98 -0
- testgen/template/gen_funny_cat_tests/gen_test_row_ct.sql +57 -0
- testgen/template/gen_funny_cat_tests/gen_test_row_ct_pct.sql +59 -0
- testgen/template/generation/gen_delete_old_tests.sql +5 -0
- testgen/template/generation/gen_insert_test_suite.sql +5 -0
- testgen/template/generation/gen_retrieve_or_insert_test_suite.sql +58 -0
- testgen/template/generation/gen_standard_test_type_list.sql +13 -0
- testgen/template/generation/gen_standard_tests.sql +48 -0
- testgen/template/get_entities/get_connection.sql +21 -0
- testgen/template/get_entities/get_connections_list.sql +9 -0
- testgen/template/get_entities/get_latest.sql +4 -0
- testgen/template/get_entities/get_profile.sql +12 -0
- testgen/template/get_entities/get_profile_info.sql +17 -0
- testgen/template/get_entities/get_profile_list.sql +17 -0
- testgen/template/get_entities/get_profile_screen.sql +275 -0
- testgen/template/get_entities/get_project_list.sql +6 -0
- testgen/template/get_entities/get_table_group_list.sql +10 -0
- testgen/template/get_entities/get_test_generation_list.sql +18 -0
- testgen/template/get_entities/get_test_info.sql +41 -0
- testgen/template/get_entities/get_test_results_for_run_cli.sql +16 -0
- testgen/template/get_entities/get_test_run_list.sql +24 -0
- testgen/template/get_entities/get_test_suite.sql +13 -0
- testgen/template/get_entities/get_test_suite_list.sql +18 -0
- testgen/template/get_entities/list_test_types.sql +4 -0
- testgen/template/observability/get_event_data.sql +23 -0
- testgen/template/observability/get_test_results.sql +41 -0
- testgen/template/observability/update_test_results_exported_to_observability.sql +12 -0
- testgen/template/parms/parms_profiling.sql +34 -0
- testgen/template/parms/parms_test_execution.sql +13 -0
- testgen/template/parms/parms_test_gen.sql +23 -0
- testgen/template/profiling/contingency_columns.sql +7 -0
- testgen/template/profiling/datatype_suggestions.sql +56 -0
- testgen/template/profiling/functional_datatype.sql +523 -0
- testgen/template/profiling/functional_tabletype_stage.sql +48 -0
- testgen/template/profiling/functional_tabletype_update.sql +8 -0
- testgen/template/profiling/pii_flag.sql +133 -0
- testgen/template/profiling/profile_anomalies_screen_column.sql +22 -0
- testgen/template/profiling/profile_anomalies_screen_multi_column.sql +58 -0
- testgen/template/profiling/profile_anomalies_screen_table.sql +22 -0
- testgen/template/profiling/profile_anomalies_screen_table_dates.sql +30 -0
- testgen/template/profiling/profile_anomalies_screen_variants.sql +40 -0
- testgen/template/profiling/profile_anomaly_types_get.sql +3 -0
- testgen/template/profiling/project_get_table_sample_count.sql +22 -0
- testgen/template/profiling/project_profile_run_record_insert.sql +8 -0
- testgen/template/profiling/project_profile_run_record_update.sql +5 -0
- testgen/template/profiling/project_profile_run_record_update_status.sql +5 -0
- testgen/template/profiling/project_update_profile_results_to_estimates.sql +32 -0
- testgen/template/profiling/refresh_anomalies.sql +33 -0
- testgen/template/profiling/refresh_data_chars_from_profiling.sql +156 -0
- testgen/template/profiling/secondary_profiling_columns.sql +12 -0
- testgen/template/profiling/secondary_profiling_delete.sql +4 -0
- testgen/template/profiling/secondary_profiling_update.sql +18 -0
- testgen/template/quick_start/populate_target_data.sql +1077 -0
- testgen/template/quick_start/recreate_target_data_schema.sql +167 -0
- testgen/template/quick_start/update_target_data.sql +100 -0
- testgen/template/updates/create_tmp_test_definition.sql +19 -0
- testgen/template/updates/get_test_def_parms.sql +38 -0
- testgen/template/updates/populate_stg_test_definitions.sql +184 -0
- testgen/template/validate_tests/ex_disable_tests_test_definitions.sql +5 -0
- testgen/template/validate_tests/ex_flag_tests_test_definitions.sql +64 -0
- testgen/template/validate_tests/ex_get_project_column_list_generic.sql +3 -0
- testgen/template/validate_tests/ex_get_test_column_list_tg.sql +65 -0
- testgen/template/validate_tests/ex_write_test_val_errors.sql +22 -0
- testgen/ui/__init__.py +0 -0
- testgen/ui/app.py +98 -0
- testgen/ui/assets/dk_logo.svg +46 -0
- testgen/ui/assets/question_mark.png +0 -0
- testgen/ui/assets/scripts.js +68 -0
- testgen/ui/assets/style.css +140 -0
- testgen/ui/bootstrap.py +109 -0
- testgen/ui/components/__init__.py +0 -0
- testgen/ui/components/frontend/css/KFOlCnqEu92Fr1MmEU9fBBc4.woff2 +0 -0
- testgen/ui/components/frontend/css/KFOlCnqEu92Fr1MmEU9fChc4EsA.woff2 +0 -0
- testgen/ui/components/frontend/css/KFOmCnqEu92Fr1Mu4mxK.woff2 +0 -0
- testgen/ui/components/frontend/css/KFOmCnqEu92Fr1Mu7GxKOzY.woff2 +0 -0
- testgen/ui/components/frontend/css/material-symbols-rounded.css +24 -0
- testgen/ui/components/frontend/css/material-symbols-rounded.woff2 +0 -0
- testgen/ui/components/frontend/css/roboto-font-faces.css +35 -0
- testgen/ui/components/frontend/css/shared.css +36 -0
- testgen/ui/components/frontend/img/dk_logo.svg +46 -0
- testgen/ui/components/frontend/index.html +17 -0
- testgen/ui/components/frontend/js/components/breadcrumbs.js +86 -0
- testgen/ui/components/frontend/js/components/button.js +66 -0
- testgen/ui/components/frontend/js/components/location.js +62 -0
- testgen/ui/components/frontend/js/components/select.js +75 -0
- testgen/ui/components/frontend/js/components/sidebar.js +358 -0
- testgen/ui/components/frontend/js/main.js +99 -0
- testgen/ui/components/frontend/js/streamlit.js +19 -0
- testgen/ui/components/frontend/js/van.min.js +1 -0
- testgen/ui/components/utils/__init__.py +0 -0
- testgen/ui/components/utils/callbacks.py +51 -0
- testgen/ui/components/utils/component.py +13 -0
- testgen/ui/components/widgets/__init__.py +6 -0
- testgen/ui/components/widgets/breadcrumbs.py +32 -0
- testgen/ui/components/widgets/location.py +65 -0
- testgen/ui/components/widgets/modal.py +97 -0
- testgen/ui/components/widgets/sidebar.py +69 -0
- testgen/ui/navigation/__init__.py +0 -0
- testgen/ui/navigation/menu.py +42 -0
- testgen/ui/navigation/page.py +20 -0
- testgen/ui/navigation/router.py +63 -0
- testgen/ui/queries/__init__.py +0 -0
- testgen/ui/queries/authentication_queries.py +47 -0
- testgen/ui/queries/connection_queries.py +121 -0
- testgen/ui/queries/profiling_queries.py +148 -0
- testgen/ui/queries/project_queries.py +9 -0
- testgen/ui/queries/table_group_queries.py +186 -0
- testgen/ui/queries/test_definition_queries.py +270 -0
- testgen/ui/queries/test_run_queries.py +32 -0
- testgen/ui/queries/test_suite_queries.py +145 -0
- testgen/ui/scripts/__init__.py +0 -0
- testgen/ui/scripts/patch_streamlit.py +111 -0
- testgen/ui/services/__init__.py +0 -0
- testgen/ui/services/authentication_service.py +119 -0
- testgen/ui/services/connection_service.py +220 -0
- testgen/ui/services/database_service.py +282 -0
- testgen/ui/services/form_service.py +1008 -0
- testgen/ui/services/javascript_service.py +44 -0
- testgen/ui/services/query_service.py +316 -0
- testgen/ui/services/string_service.py +12 -0
- testgen/ui/services/table_group_service.py +130 -0
- testgen/ui/services/test_definition_service.py +117 -0
- testgen/ui/services/test_run_service.py +13 -0
- testgen/ui/services/test_suite_service.py +76 -0
- testgen/ui/services/toolbar_service.py +77 -0
- testgen/ui/session.py +46 -0
- testgen/ui/views/__init__.py +0 -0
- testgen/ui/views/app_log_modal.py +92 -0
- testgen/ui/views/connections.py +72 -0
- testgen/ui/views/connections_base.py +367 -0
- testgen/ui/views/login.py +40 -0
- testgen/ui/views/not_found.py +16 -0
- testgen/ui/views/overview.py +34 -0
- testgen/ui/views/profiling_anomalies.py +501 -0
- testgen/ui/views/profiling_details.py +335 -0
- testgen/ui/views/profiling_modal.py +40 -0
- testgen/ui/views/profiling_results.py +206 -0
- testgen/ui/views/profiling_summary.py +177 -0
- testgen/ui/views/project_settings.py +74 -0
- testgen/ui/views/table_groups.py +530 -0
- testgen/ui/views/test_definitions.py +1020 -0
- testgen/ui/views/test_results.py +908 -0
- testgen/ui/views/test_runs.py +195 -0
- testgen/ui/views/test_suites.py +545 -0
- testgen/utils/__init__.py +0 -0
- testgen/utils/plugins.py +17 -0
- testgen/utils/singleton.py +14 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# ruff: noqa: S105
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import logging
|
|
5
|
+
import typing
|
|
6
|
+
|
|
7
|
+
import extra_streamlit_components as stx
|
|
8
|
+
import jwt
|
|
9
|
+
import streamlit as st
|
|
10
|
+
|
|
11
|
+
from testgen.common.encrypt import encrypt_ui_password
|
|
12
|
+
from testgen.ui.queries import authentication_queries
|
|
13
|
+
from testgen.ui.session import session
|
|
14
|
+
|
|
15
|
+
RoleType = typing.Literal["admin", "edit", "read"]
|
|
16
|
+
|
|
17
|
+
JWT_HASHING_KEY = "dk_signature_key"
|
|
18
|
+
AUTH_TOKEN_COOKIE_NAME = "dk_cookie_name"
|
|
19
|
+
AUTH_TOKEN_EXPIRATION_DAYS = 5
|
|
20
|
+
|
|
21
|
+
LOG = logging.getLogger("testgen")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load_user_session() -> None:
|
|
25
|
+
cookies = stx.CookieManager(key="testgen.cookies.get")
|
|
26
|
+
token = cookies.get(AUTH_TOKEN_COOKIE_NAME)
|
|
27
|
+
if token is not None:
|
|
28
|
+
try:
|
|
29
|
+
token = jwt.decode(token, JWT_HASHING_KEY, algorithms=["HS256"])
|
|
30
|
+
if token["exp_date"] > datetime.datetime.utcnow().timestamp():
|
|
31
|
+
start_user_session(token["name"], token["username"])
|
|
32
|
+
except Exception:
|
|
33
|
+
LOG.debug("Invalid auth token found on cookies", exc_info=True, stack_info=True)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def start_user_session(name: str, username: str) -> None:
|
|
37
|
+
session.name = name
|
|
38
|
+
session.username = username
|
|
39
|
+
session.auth_role = get_role_for_user(get_auth_data(), username)
|
|
40
|
+
session.authentication_status = True
|
|
41
|
+
if not session.current_page or session.current_page == "login":
|
|
42
|
+
session.current_page = "overview"
|
|
43
|
+
session.current_page_args = {}
|
|
44
|
+
session.logging_out = False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def end_user_session() -> None:
|
|
48
|
+
session.auth_role = None
|
|
49
|
+
session.authentication_status = None
|
|
50
|
+
session.current_page = "login"
|
|
51
|
+
session.current_page_args = {}
|
|
52
|
+
session.logging_out = True
|
|
53
|
+
|
|
54
|
+
del session.name
|
|
55
|
+
del session.username
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def add_user(user):
|
|
59
|
+
encrypted_password = encrypt_ui_password(user["password"])
|
|
60
|
+
schema = st.session_state["dbschema"]
|
|
61
|
+
authentication_queries.add_user(schema, user, encrypted_password)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def delete_users(user_ids):
|
|
65
|
+
schema = st.session_state["dbschema"]
|
|
66
|
+
return authentication_queries.delete_users(schema, user_ids)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def edit_user(user):
|
|
70
|
+
encrypted_password = encrypt_ui_password(user["password"])
|
|
71
|
+
schema = st.session_state["dbschema"]
|
|
72
|
+
authentication_queries.edit_user(schema, user, encrypted_password)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_auth_data():
|
|
76
|
+
auth_data = authentication_queries.get_users(session.dbschema)
|
|
77
|
+
|
|
78
|
+
usernames = {}
|
|
79
|
+
preauthorized_list = []
|
|
80
|
+
|
|
81
|
+
for item in auth_data.itertuples():
|
|
82
|
+
usernames[item.username] = {
|
|
83
|
+
"email": item.email,
|
|
84
|
+
"name": item.name,
|
|
85
|
+
"password": item.password,
|
|
86
|
+
"role": item.role,
|
|
87
|
+
}
|
|
88
|
+
if item.preauthorized:
|
|
89
|
+
preauthorized_list.append(item.email)
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
"credentials": {"usernames": usernames},
|
|
93
|
+
"cookie": {"expiry_days": AUTH_TOKEN_EXPIRATION_DAYS, "key": JWT_HASHING_KEY, "name": AUTH_TOKEN_COOKIE_NAME},
|
|
94
|
+
"preauthorized": {"emails": preauthorized_list},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_users():
|
|
99
|
+
return authentication_queries.get_users(session.dbschema)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def get_role_for_user(auth_data, username):
|
|
103
|
+
return auth_data["credentials"]["usernames"][username]["role"]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def current_user_has_admin_role():
|
|
107
|
+
return session.auth_role == "admin"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def current_user_has_edit_role():
|
|
111
|
+
return session.auth_role in ("edit", "admin")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def current_user_has_read_role():
|
|
115
|
+
return not session.auth_role or session.auth_role == "read"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def current_user_has_role(role: RoleType) -> bool:
|
|
119
|
+
return session.auth_role == role
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import streamlit as st
|
|
2
|
+
|
|
3
|
+
import testgen.ui.queries.connection_queries as connection_queries
|
|
4
|
+
import testgen.ui.services.table_group_service as table_group_service
|
|
5
|
+
from testgen.commands.run_profiling_bridge import InitializeProfilingSQL
|
|
6
|
+
from testgen.commands.run_setup_profiling_tools import run_setup_profiling_tools
|
|
7
|
+
from testgen.common.database.database_service import (
|
|
8
|
+
AssignConnectParms,
|
|
9
|
+
RetrieveDBResultsToList,
|
|
10
|
+
empty_cache,
|
|
11
|
+
get_db_type,
|
|
12
|
+
get_flavor_service,
|
|
13
|
+
)
|
|
14
|
+
from testgen.common.encrypt import DecryptText, EncryptText
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_by_id(connection_id, hide_passwords: bool = True):
|
|
18
|
+
connections_df = connection_queries.get_by_id(connection_id)
|
|
19
|
+
decrypt_connections(connections_df, hide_passwords)
|
|
20
|
+
connection = connections_df.to_dict(orient="records")[0]
|
|
21
|
+
return connection
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_connections(project_code, hide_passwords: bool = False):
|
|
25
|
+
connections = connection_queries.get_connections(project_code)
|
|
26
|
+
decrypt_connections(connections, hide_passwords)
|
|
27
|
+
return connections
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def decrypt_connections(connections, hide_passwords: bool = False):
|
|
31
|
+
for index, connection in connections.iterrows():
|
|
32
|
+
if hide_passwords:
|
|
33
|
+
password = "***" # noqa S105
|
|
34
|
+
private_key = "***" # S105
|
|
35
|
+
private_key_passphrase = "***" # noqa S105
|
|
36
|
+
else:
|
|
37
|
+
password = DecryptText(connection["project_pw_encrypted"]) if connection["project_pw_encrypted"] else None
|
|
38
|
+
private_key = DecryptText(connection["private_key"]) if connection["private_key"] else None
|
|
39
|
+
private_key_passphrase = DecryptText(connection["private_key_passphrase"]) if connection["private_key_passphrase"] else ""
|
|
40
|
+
connections.at[index, "password"] = password
|
|
41
|
+
connections.at[index, "private_key"] = private_key
|
|
42
|
+
connections.at[index, "private_key_passphrase"] = private_key_passphrase
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def encrypt_credentials(connection):
|
|
46
|
+
encrypted_password = EncryptText(connection["password"]) if connection["password"] else None
|
|
47
|
+
encrypted_private_key = EncryptText(connection["private_key"]) if connection["private_key"] else None
|
|
48
|
+
encrypted_private_key_passphrase = EncryptText(connection["private_key_passphrase"]) if connection["private_key_passphrase"] else None
|
|
49
|
+
return encrypted_password, encrypted_private_key, encrypted_private_key_passphrase
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def edit_connection(connection):
|
|
53
|
+
empty_cache()
|
|
54
|
+
schema = st.session_state["dbschema"]
|
|
55
|
+
connection = pre_save_connection_process(connection)
|
|
56
|
+
encrypted_password, encrypted_private_key, encrypted_private_key_passphrase = encrypt_credentials(connection)
|
|
57
|
+
connection_queries.edit_connection(schema, connection, encrypted_password, encrypted_private_key, encrypted_private_key_passphrase)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def add_connection(connection):
|
|
61
|
+
empty_cache()
|
|
62
|
+
schema = st.session_state["dbschema"]
|
|
63
|
+
connection = pre_save_connection_process(connection)
|
|
64
|
+
encrypted_password, encrypted_private_key, encrypted_private_key_passphrase = encrypt_credentials(connection)
|
|
65
|
+
connection_queries.add_connection(schema, connection, encrypted_password, encrypted_private_key, encrypted_private_key_passphrase)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def pre_save_connection_process(connection):
|
|
69
|
+
if connection["connect_by_url"]:
|
|
70
|
+
url = connection["url"]
|
|
71
|
+
if url:
|
|
72
|
+
url_sections = url.split("/")
|
|
73
|
+
if len(url_sections) > 0:
|
|
74
|
+
host_port = url_sections[0]
|
|
75
|
+
host_port_sections = host_port.split(":")
|
|
76
|
+
if len(host_port_sections) > 0:
|
|
77
|
+
connection["project_host"] = host_port_sections[0]
|
|
78
|
+
connection["project_port"] = "".join(host_port_sections[1:])
|
|
79
|
+
else:
|
|
80
|
+
connection["project_host"] = host_port
|
|
81
|
+
connection["project_port"] = ""
|
|
82
|
+
if len(url_sections) > 1:
|
|
83
|
+
connection["project_db"] = url_sections[1]
|
|
84
|
+
return connection
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def delete_connections(connection_ids):
|
|
88
|
+
empty_cache()
|
|
89
|
+
schema = st.session_state["dbschema"]
|
|
90
|
+
return connection_queries.delete_connections(schema, connection_ids)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def cascade_delete(connection_ids, dry_run=False):
|
|
94
|
+
schema = st.session_state["dbschema"]
|
|
95
|
+
can_be_deleted = True
|
|
96
|
+
table_group_names = get_table_group_names_by_connection(connection_ids)
|
|
97
|
+
connection_has_dependencies = table_group_names is not None and len(table_group_names) > 0
|
|
98
|
+
if connection_has_dependencies:
|
|
99
|
+
can_be_deleted = False
|
|
100
|
+
if not dry_run:
|
|
101
|
+
if connection_has_dependencies:
|
|
102
|
+
table_group_service.cascade_delete(table_group_names)
|
|
103
|
+
connection_queries.delete_connections(schema, connection_ids)
|
|
104
|
+
return can_be_deleted
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def are_connections_in_use(connection_ids):
|
|
108
|
+
table_group_names = get_table_group_names_by_connection(connection_ids)
|
|
109
|
+
table_groups_in_use = table_group_service.are_table_groups_in_use(table_group_names)
|
|
110
|
+
return table_groups_in_use
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def get_table_group_names_by_connection(connection_ids):
|
|
114
|
+
if not connection_ids:
|
|
115
|
+
return []
|
|
116
|
+
schema = st.session_state["dbschema"]
|
|
117
|
+
table_group_names = connection_queries.get_table_group_names_by_connection(schema, connection_ids)
|
|
118
|
+
return table_group_names.to_dict()["table_groups_name"].values()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def init_profiling_sql(project_code, connection, table_group_schema=None):
|
|
122
|
+
# get connection data
|
|
123
|
+
empty_cache()
|
|
124
|
+
connection_id = str(connection["connection_id"]) if connection["connection_id"] else None
|
|
125
|
+
sql_flavor = connection["sql_flavor"]
|
|
126
|
+
url = connection["url"]
|
|
127
|
+
connect_by_url = connection["connect_by_url"]
|
|
128
|
+
connect_by_key = connection["connect_by_key"]
|
|
129
|
+
private_key = connection["private_key"]
|
|
130
|
+
private_key_passphrase = connection["private_key_passphrase"]
|
|
131
|
+
project_host = connection["project_host"]
|
|
132
|
+
project_port = connection["project_port"]
|
|
133
|
+
project_db = connection["project_db"]
|
|
134
|
+
project_user = connection["project_user"]
|
|
135
|
+
project_qc_schema = connection["project_qc_schema"]
|
|
136
|
+
password = connection["password"]
|
|
137
|
+
|
|
138
|
+
# prepare the profiling query
|
|
139
|
+
clsProfiling = InitializeProfilingSQL(project_code, sql_flavor)
|
|
140
|
+
|
|
141
|
+
AssignConnectParms(
|
|
142
|
+
project_code,
|
|
143
|
+
connection_id,
|
|
144
|
+
project_host,
|
|
145
|
+
project_port,
|
|
146
|
+
project_db,
|
|
147
|
+
table_group_schema if table_group_schema else project_qc_schema,
|
|
148
|
+
project_user,
|
|
149
|
+
sql_flavor,
|
|
150
|
+
url,
|
|
151
|
+
connect_by_url,
|
|
152
|
+
connect_by_key,
|
|
153
|
+
private_key,
|
|
154
|
+
private_key_passphrase,
|
|
155
|
+
connectname="PROJECT",
|
|
156
|
+
password=password,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return clsProfiling
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_qc_connection(project_code, connection, init_profiling=True):
|
|
163
|
+
qc_results = {}
|
|
164
|
+
|
|
165
|
+
if init_profiling:
|
|
166
|
+
init_profiling_sql(project_code, connection)
|
|
167
|
+
|
|
168
|
+
project_qc_schema = connection["project_qc_schema"]
|
|
169
|
+
query_isnum_true = f"select {project_qc_schema}.fndk_isnum('32')"
|
|
170
|
+
query_isnum_true_result_raw = RetrieveDBResultsToList("PROJECT", query_isnum_true)
|
|
171
|
+
isnum_true_result = query_isnum_true_result_raw[0][0][0] == 1
|
|
172
|
+
qc_results["isnum_true_result"] = isnum_true_result
|
|
173
|
+
|
|
174
|
+
query_isnum_false = f"select {project_qc_schema}.fndk_isnum('HELLO')"
|
|
175
|
+
query_isnum_false_result_raw = RetrieveDBResultsToList("PROJECT", query_isnum_false)
|
|
176
|
+
isnum_false_result = query_isnum_false_result_raw[0][0][0] == 0
|
|
177
|
+
qc_results["isnum_false_result"] = isnum_false_result
|
|
178
|
+
|
|
179
|
+
query_isdate_true = f"select {project_qc_schema}.fndk_isdate('2013-05-18')"
|
|
180
|
+
query_isdate_true_result_raw = RetrieveDBResultsToList("PROJECT", query_isdate_true)
|
|
181
|
+
isdate_true_result = query_isdate_true_result_raw[0][0][0] == 1
|
|
182
|
+
qc_results["isdate_true_result"] = isdate_true_result
|
|
183
|
+
|
|
184
|
+
query_isdate_false = f"select {project_qc_schema}.fndk_isdate('HELLO')"
|
|
185
|
+
query_isdate_false_result_raw = RetrieveDBResultsToList("PROJECT", query_isdate_false)
|
|
186
|
+
isdate_false_result = query_isdate_false_result_raw[0][0][0] == 0
|
|
187
|
+
qc_results["isdate_false_result"] = isdate_false_result
|
|
188
|
+
|
|
189
|
+
return qc_results
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def create_qc_schema(connection_id, create_qc_schema, db_user, db_password, skip_granting_privileges, admin_private_key_passphrase=None, admin_private_key=None, user_role=None):
|
|
193
|
+
dry_run = False
|
|
194
|
+
empty_cache()
|
|
195
|
+
run_setup_profiling_tools(connection_id, dry_run, create_qc_schema, db_user, db_password, skip_granting_privileges, admin_private_key_passphrase, admin_private_key, user_role)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def form_overwritten_connection_url(connection):
|
|
199
|
+
flavor = connection["sql_flavor"]
|
|
200
|
+
|
|
201
|
+
connection_credentials = {
|
|
202
|
+
"flavor": flavor,
|
|
203
|
+
"user": "<user>",
|
|
204
|
+
"host": connection["project_host"],
|
|
205
|
+
"port": connection["project_port"],
|
|
206
|
+
"dbname": connection["project_db"],
|
|
207
|
+
"url": None,
|
|
208
|
+
"connect_by_url": None,
|
|
209
|
+
"connect_by_key": connection["connect_by_key"],
|
|
210
|
+
"private_key": None,
|
|
211
|
+
"private_key_passphrase": "",
|
|
212
|
+
"dbschema": "",
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
db_type = get_db_type(flavor)
|
|
216
|
+
flavor_service = get_flavor_service(db_type)
|
|
217
|
+
flavor_service.init(connection_credentials)
|
|
218
|
+
connection_string = flavor_service.get_connection_string("<password>")
|
|
219
|
+
|
|
220
|
+
return connection_string
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
from urllib.parse import quote_plus
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from sqlalchemy import create_engine, text
|
|
5
|
+
|
|
6
|
+
from testgen.common.credentials import (
|
|
7
|
+
get_tg_db,
|
|
8
|
+
get_tg_host,
|
|
9
|
+
get_tg_password,
|
|
10
|
+
get_tg_port,
|
|
11
|
+
get_tg_schema,
|
|
12
|
+
get_tg_username,
|
|
13
|
+
)
|
|
14
|
+
from testgen.common.database.database_service import get_flavor_service
|
|
15
|
+
from testgen.common.encrypt import DecryptText
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
Shared database access and utility functions
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_schema():
|
|
23
|
+
return get_tg_schema()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _start_engine():
|
|
27
|
+
# TestGen database
|
|
28
|
+
dbhost = get_tg_host()
|
|
29
|
+
dbport = get_tg_port()
|
|
30
|
+
dbname = get_tg_db()
|
|
31
|
+
# User Information
|
|
32
|
+
dbuser = get_tg_username()
|
|
33
|
+
dbpw = get_tg_password()
|
|
34
|
+
|
|
35
|
+
conn_str = "postgresql://" + dbuser + ":" + quote_plus(dbpw) + "@" + dbhost + ":" + dbport + "/" + dbname
|
|
36
|
+
return create_engine(conn_str)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _make_connection():
|
|
40
|
+
engine = _start_engine()
|
|
41
|
+
return engine
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def make_header_db_friendly(str_header):
|
|
45
|
+
return str_header.replace(" ", "_").lower()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def make_value_db_friendly(value):
|
|
49
|
+
if value is None or pd.isna(value):
|
|
50
|
+
newval = "NULL"
|
|
51
|
+
else:
|
|
52
|
+
newval = str(value) if isinstance(value, int | float) else f"'{value}'"
|
|
53
|
+
return newval
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def retrieve_data(str_sql):
|
|
57
|
+
tg_engine = _start_engine()
|
|
58
|
+
# Retrieve data from Postgres
|
|
59
|
+
return pd.read_sql_query(str_sql, tg_engine)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def retrieve_data_list(str_sql):
|
|
63
|
+
tg_engine = _start_engine()
|
|
64
|
+
# Retrieve data from Postgres
|
|
65
|
+
with tg_engine.connect() as con:
|
|
66
|
+
return con.execute(text(str_sql)).fetchall()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def retrieve_single_result(str_sql):
|
|
70
|
+
tg_engine = _start_engine()
|
|
71
|
+
with tg_engine.connect() as con:
|
|
72
|
+
lstResult = con.execute(text(str_sql)).fetchone()
|
|
73
|
+
if lstResult:
|
|
74
|
+
return lstResult[0]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def execute_sql(str_sql):
|
|
78
|
+
if str_sql > "":
|
|
79
|
+
tg_engine = _start_engine()
|
|
80
|
+
tg_engine.execute(text(str_sql))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def execute_sql_raw(str_sql):
|
|
84
|
+
# For special cases where SQLAlchemy can't handle query syntax
|
|
85
|
+
if str_sql > "":
|
|
86
|
+
tg_engine = _start_engine()
|
|
87
|
+
con = tg_engine.raw_connection()
|
|
88
|
+
with con.cursor() as cur:
|
|
89
|
+
cur.execute(str_sql)
|
|
90
|
+
con.commit()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _get_df_edits(df_original: pd.DataFrame, df_edited: pd.DataFrame, lst_id_columns: list) -> tuple:
|
|
94
|
+
# Rows in df_edited that exist in df_original but have had any column changed
|
|
95
|
+
# based on composite ID columns
|
|
96
|
+
|
|
97
|
+
# Merge the two dataframes based on the composite ID columns
|
|
98
|
+
merged_df = df_edited.merge(df_original, on=lst_id_columns, how="outer", indicator=True, suffixes=("", "_original"))
|
|
99
|
+
# Filter the merged dataframe to only keep rows that are changed
|
|
100
|
+
# Step 1: Filter rows that exist in both dataframes
|
|
101
|
+
both_rows = merged_df[merged_df["_merge"] == "both"]
|
|
102
|
+
|
|
103
|
+
# Step 2: Identify changed rows
|
|
104
|
+
def has_changes(row):
|
|
105
|
+
for col in df_original.columns:
|
|
106
|
+
# Skip the ID columns
|
|
107
|
+
if col in lst_id_columns:
|
|
108
|
+
continue
|
|
109
|
+
if row[col] != row[col + "_original"]:
|
|
110
|
+
return True
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
changed_rows_mask = both_rows.apply(has_changes, axis=1)
|
|
114
|
+
|
|
115
|
+
# Step 3: Combine the filters
|
|
116
|
+
changed_rows = both_rows[changed_rows_mask]
|
|
117
|
+
|
|
118
|
+
# All rows in df_edited that are newly created and don't exist in df_original
|
|
119
|
+
new_rows = merged_df[merged_df["_merge"] == "left_only"].drop(
|
|
120
|
+
columns=["_merge"] + [col + "_original" for col in df_original.columns if col not in lst_id_columns]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# All rows in df_original that have been deleted from df_edited
|
|
124
|
+
deleted_rows = merged_df[merged_df["_merge"] == "right_only"][df_original.columns]
|
|
125
|
+
|
|
126
|
+
return changed_rows, new_rows, deleted_rows
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _gen_df_update_sql(
|
|
130
|
+
changed_rows: pd.DataFrame, table_name: str, lst_id_columns: list, no_update_columns: list
|
|
131
|
+
) -> list:
|
|
132
|
+
# Generate a list of SQL UPDATE statements based on the changed rows.
|
|
133
|
+
|
|
134
|
+
# Extract the original column names by removing the "_original" suffix
|
|
135
|
+
original_columns = [col.replace("_original", "") for col in changed_rows.columns if col.endswith("_original")]
|
|
136
|
+
# Drop columns we aren't updating from list
|
|
137
|
+
update_columns = [col for col in original_columns if col not in no_update_columns]
|
|
138
|
+
|
|
139
|
+
# Generate SQL UPDATE statements
|
|
140
|
+
sql_statements = []
|
|
141
|
+
for _, row in changed_rows.iterrows():
|
|
142
|
+
set_statements = []
|
|
143
|
+
for col in update_columns:
|
|
144
|
+
# If the value is different from the original value
|
|
145
|
+
if row[col] != row[col + "_original"]:
|
|
146
|
+
value = make_value_db_friendly(row[col])
|
|
147
|
+
set_statements.append(f"{col} = {value}")
|
|
148
|
+
|
|
149
|
+
# Handle composite keys for the WHERE clause
|
|
150
|
+
where_statements = []
|
|
151
|
+
for col in lst_id_columns:
|
|
152
|
+
value = make_value_db_friendly(row[col])
|
|
153
|
+
# value = f"'{row[col]}'" if isinstance(row[col], str) else row[col]
|
|
154
|
+
where_statements.append(f"{col} = {value}")
|
|
155
|
+
|
|
156
|
+
update_statement = f"UPDATE {get_schema()}.{table_name} SET {', '.join(set_statements)} WHERE {' AND '.join(where_statements)};"
|
|
157
|
+
sql_statements.append(update_statement)
|
|
158
|
+
|
|
159
|
+
return sql_statements
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _gen_df_delete_sql(deleted_rows: pd.DataFrame, table_name: str, lst_id_columns: list) -> list:
|
|
163
|
+
# Generate a list of SQL DELETE statements based on the deleted rows.
|
|
164
|
+
|
|
165
|
+
# Generate SQL DELETE statements
|
|
166
|
+
sql_statements = []
|
|
167
|
+
for _, row in deleted_rows.iterrows():
|
|
168
|
+
# Handle composite keys for the WHERE clause
|
|
169
|
+
where_statements = []
|
|
170
|
+
for col in lst_id_columns:
|
|
171
|
+
value = make_value_db_friendly(row[col])
|
|
172
|
+
# value = f"'{row[col]}'" if isinstance(row[col], str) else row[col]
|
|
173
|
+
where_statements.append(f"{col} = {value}")
|
|
174
|
+
|
|
175
|
+
delete_statement = f"DELETE FROM {get_schema()}.{table_name} WHERE {' AND '.join(where_statements)};"
|
|
176
|
+
sql_statements.append(delete_statement)
|
|
177
|
+
|
|
178
|
+
return sql_statements
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _gen_insert_sql(
|
|
182
|
+
new_rows: pd.DataFrame,
|
|
183
|
+
table_name: str,
|
|
184
|
+
lst_id_columns: list,
|
|
185
|
+
no_update_columns: list,
|
|
186
|
+
dct_hard_default_columns: dict,
|
|
187
|
+
) -> str:
|
|
188
|
+
# Generate a SQL INSERT statement for the new rows, ensuring strings are properly quoted.
|
|
189
|
+
|
|
190
|
+
# Remove the id column as it will be generated by the server
|
|
191
|
+
if lst_id_columns:
|
|
192
|
+
new_rows = new_rows.drop(columns=lst_id_columns)
|
|
193
|
+
if no_update_columns:
|
|
194
|
+
# Remove columns we aren't updating
|
|
195
|
+
new_rows = new_rows.drop(columns=no_update_columns)
|
|
196
|
+
if dct_hard_default_columns:
|
|
197
|
+
# Add and default all columns
|
|
198
|
+
new_rows = new_rows.assign(**dct_hard_default_columns)
|
|
199
|
+
|
|
200
|
+
# Generate column names and values for the INSERT statement
|
|
201
|
+
columns = ", ".join(new_rows.columns)
|
|
202
|
+
|
|
203
|
+
# Ensure strings are quoted
|
|
204
|
+
values = []
|
|
205
|
+
for _, row in new_rows.iterrows():
|
|
206
|
+
row_values = []
|
|
207
|
+
for val in row:
|
|
208
|
+
row_values.append(make_value_db_friendly(val))
|
|
209
|
+
# if isinstance(val, str):
|
|
210
|
+
# row_values.append(f"'{val}'")
|
|
211
|
+
# else:
|
|
212
|
+
# row_values.append(str(val))
|
|
213
|
+
values.append(f"({', '.join(row_values)})")
|
|
214
|
+
|
|
215
|
+
if values:
|
|
216
|
+
values_str = ", ".join(values)
|
|
217
|
+
# Construct the SQL INSERT statement
|
|
218
|
+
sql_statement = f"INSERT INTO {get_schema()}.{table_name} ({columns}) VALUES {values_str};"
|
|
219
|
+
return sql_statement
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def apply_df_edits(df_original, df_edited, str_table, lst_id_columns, no_update_columns, dct_hard_default_columns):
|
|
223
|
+
booStatus = False
|
|
224
|
+
df_changed, df_new, df_deleted = _get_df_edits(df_original, df_edited, lst_id_columns)
|
|
225
|
+
|
|
226
|
+
# Generate SQL UPDATE statements
|
|
227
|
+
lst_update_SQL = _gen_df_update_sql(df_changed, str_table, lst_id_columns, no_update_columns)
|
|
228
|
+
if lst_update_SQL:
|
|
229
|
+
for str_sql in lst_update_SQL:
|
|
230
|
+
execute_sql(str_sql)
|
|
231
|
+
booStatus = True
|
|
232
|
+
# Generate SQL DELETE statements
|
|
233
|
+
lst_delete_SQL = _gen_df_delete_sql(df_deleted, str_table, lst_id_columns)
|
|
234
|
+
if lst_delete_SQL:
|
|
235
|
+
for str_sql in lst_delete_SQL:
|
|
236
|
+
execute_sql(str_sql)
|
|
237
|
+
booStatus = True
|
|
238
|
+
# Generate SQL INSERT statements
|
|
239
|
+
str_insert_sql = _gen_insert_sql(df_new, str_table, lst_id_columns, no_update_columns, dct_hard_default_columns)
|
|
240
|
+
if str_insert_sql:
|
|
241
|
+
execute_sql(str_insert_sql)
|
|
242
|
+
booStatus = True
|
|
243
|
+
|
|
244
|
+
return booStatus
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _start_target_db_engine(flavor, host, port, db_name, user, password, url, connect_by_url, connect_by_key, private_key, private_key_passphrase):
|
|
248
|
+
connection_params = {
|
|
249
|
+
"flavor": flavor if flavor != "redshift" else "postgresql",
|
|
250
|
+
"user": user,
|
|
251
|
+
"host": host,
|
|
252
|
+
"port": port,
|
|
253
|
+
"dbname": db_name,
|
|
254
|
+
"url": url,
|
|
255
|
+
"connect_by_url": connect_by_url,
|
|
256
|
+
"connect_by_key": connect_by_key,
|
|
257
|
+
"private_key": private_key,
|
|
258
|
+
"private_key_passphrase": private_key_passphrase,
|
|
259
|
+
"dbschema": None,
|
|
260
|
+
}
|
|
261
|
+
flavor_service = get_flavor_service(flavor)
|
|
262
|
+
flavor_service.init(connection_params)
|
|
263
|
+
connection_string = flavor_service.get_connection_string(password)
|
|
264
|
+
connect_args = {"connect_timeout": 3600}
|
|
265
|
+
connect_args.update(flavor_service.get_connect_args())
|
|
266
|
+
return create_engine(connection_string, connect_args=connect_args)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def retrieve_target_db_data(flavor, host, port, db_name, user, password, url, connect_by_url, connect_by_key, private_key, private_key_passphrase, sql_query, decrypt=False):
|
|
270
|
+
if decrypt:
|
|
271
|
+
password = DecryptText(password)
|
|
272
|
+
db_engine = _start_target_db_engine(flavor, host, port, db_name, user, password, url, connect_by_url, connect_by_key, private_key, private_key_passphrase)
|
|
273
|
+
with db_engine.connect() as connection:
|
|
274
|
+
query_result = connection.execute(text(sql_query))
|
|
275
|
+
return query_result.fetchall()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def retrieve_target_db_df(flavor, host, port, db_name, user, password, sql_query, url, connect_by_url, connect_by_key, private_key, private_key_passphrase):
|
|
279
|
+
if password:
|
|
280
|
+
password = DecryptText(password)
|
|
281
|
+
db_engine = _start_target_db_engine(flavor, host, port, db_name, user, password, url, connect_by_url, connect_by_key, private_key, private_key_passphrase)
|
|
282
|
+
return pd.read_sql_query(text(sql_query), db_engine)
|