mage-ai 0.9.68__py3-none-any.whl → 0.9.70__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.
Potentially problematic release.
This version of mage-ai might be problematic. Click here for more details.
- mage_ai/api/policies/BackfillPolicy.py +1 -0
- mage_ai/api/policies/PipelinePolicy.py +1 -0
- mage_ai/api/policies/WorkspacePolicy.py +1 -0
- mage_ai/api/presenters/BackfillPresenter.py +1 -0
- mage_ai/api/resources/GitBranchResource.py +56 -23
- mage_ai/api/resources/GitCustomBranchResource.py +29 -1
- mage_ai/api/resources/OauthResource.py +1 -1
- mage_ai/api/resources/PipelineResource.py +11 -5
- mage_ai/api/resources/PipelineRunResource.py +41 -4
- mage_ai/api/resources/PipelineScheduleResource.py +4 -0
- mage_ai/api/resources/PullRequestResource.py +6 -4
- mage_ai/api/resources/SeedResource.py +2 -1
- mage_ai/api/resources/SessionResource.py +13 -1
- mage_ai/api/resources/WorkspaceResource.py +5 -4
- mage_ai/authentication/permissions/constants.py +2 -0
- mage_ai/authentication/permissions/seed.py +32 -21
- mage_ai/authentication/providers/active_directory.py +4 -3
- mage_ai/authentication/providers/okta.py +22 -83
- mage_ai/cache/block_action_object/__init__.py +1 -1
- mage_ai/cluster_manager/kubernetes/workload_manager.py +52 -1
- mage_ai/cluster_manager/workspace/base.py +6 -0
- mage_ai/cluster_manager/workspace/kubernetes.py +22 -1
- mage_ai/command_center/applications/utils.py +2 -2
- mage_ai/command_center/presenters/text.py +1 -1
- mage_ai/data_preparation/executors/k8s_block_executor.py +30 -7
- mage_ai/data_preparation/executors/k8s_pipeline_executor.py +30 -7
- mage_ai/data_preparation/executors/streaming_pipeline_executor.py +78 -8
- mage_ai/data_preparation/git/__init__.py +50 -22
- mage_ai/data_preparation/git/api.py +62 -7
- mage_ai/data_preparation/git/utils.py +45 -21
- mage_ai/data_preparation/models/block/__init__.py +31 -8
- mage_ai/data_preparation/models/block/data_integration/mixins.py +16 -5
- mage_ai/data_preparation/models/block/dynamic/child.py +3 -0
- mage_ai/data_preparation/models/block/dynamic/utils.py +9 -4
- mage_ai/data_preparation/models/block/dynamic/variables.py +2 -2
- mage_ai/data_preparation/models/block/extension/utils.py +1 -0
- mage_ai/data_preparation/models/block/global_data_product/__init__.py +25 -2
- mage_ai/data_preparation/models/block/integration/__init__.py +1 -1
- mage_ai/data_preparation/models/block/remote/__init__.py +0 -0
- mage_ai/data_preparation/models/block/remote/models.py +58 -0
- mage_ai/data_preparation/models/block/sql/__init__.py +1 -1
- mage_ai/data_preparation/models/block/utils.py +38 -0
- mage_ai/data_preparation/models/constants.py +2 -0
- mage_ai/data_preparation/models/global_data_product/__init__.py +12 -0
- mage_ai/data_preparation/models/pipeline.py +31 -11
- mage_ai/data_preparation/models/triggers/__init__.py +4 -2
- mage_ai/data_preparation/models/utils.py +6 -0
- mage_ai/data_preparation/models/variable.py +18 -4
- mage_ai/data_preparation/repo_manager.py +3 -2
- mage_ai/data_preparation/shared/utils.py +1 -1
- mage_ai/data_preparation/storage/local_storage.py +12 -6
- mage_ai/data_preparation/templates/data_exporters/mysql.py +2 -2
- mage_ai/data_preparation/templates/data_exporters/oracledb.py +27 -0
- mage_ai/data_preparation/templates/repo/metadata.yaml +1 -0
- mage_ai/io/bigquery.py +131 -58
- mage_ai/io/mysql.py +38 -6
- mage_ai/io/snowflake.py +152 -29
- mage_ai/orchestration/db/migrations/versions/42a14d6143f1_update_token_column_type.py +54 -0
- mage_ai/orchestration/db/models/oauth.py +14 -13
- mage_ai/orchestration/db/models/schedules.py +30 -2
- mage_ai/orchestration/job_manager.py +6 -0
- mage_ai/orchestration/notification/sender.py +37 -15
- mage_ai/orchestration/pipeline_scheduler_original.py +48 -31
- mage_ai/orchestration/queue/celery_queue.py +8 -1
- mage_ai/orchestration/queue/process_queue.py +67 -4
- mage_ai/orchestration/queue/queue.py +8 -0
- mage_ai/orchestration/triggers/api.py +29 -1
- mage_ai/orchestration/triggers/global_data_product.py +9 -4
- mage_ai/orchestration/triggers/utils.py +10 -1
- mage_ai/orchestration/utils/resources.py +3 -0
- mage_ai/server/api/downloads.py +4 -1
- mage_ai/server/api/runs.py +151 -0
- mage_ai/server/constants.py +1 -1
- mage_ai/server/frontend_dist/404.html +6 -6
- mage_ai/server/frontend_dist/_next/static/{i8pymuJDTVHdWjUP1QSh1 → RhDiJSkcjCsh4xxX4BFBk}/_buildManifest.js +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/1557-b3502f3f1aa92ac7.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/2717-d9200be634dd6766.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/3548-9d26185b3fb663b1.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/5699-6d708c6b2153ea08.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/7361-8a23dd8360593e7a.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/7966-b9b85ba10667e654.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/9624-8b8e100079ab69e1.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-2a69553d8c6eeb53.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/index-4e12783b064c1cfe.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-4bfc84ff07d7656f.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipeline-runs-a66b4c7641ae03eb.js → frontend_dist/_next/static/chunks/pages/pipeline-runs-6d183f91a2ff6668.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-7181b086c93784d2.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-38e1fbcfbfc1014e.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-b645a6d13ab9fe3a.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-59aca25a5b1d3998.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-eb11c5390c982b49.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-cb88fd075a357fcf.js → triggers-4612d15a65c35912.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-3591d035bb3bb2b8.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-32985f3f7c7dd3ab.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/settings-c2e9ef989c8bfa73.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-349af617d05f001b.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-60d01d3887e31136.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{users-86814e581acaf5db.js → users-a4db8710f703c729.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/triggers-9cba3211434a8966.js → frontend_dist/_next/static/chunks/pages/triggers-a599c6ac89be8c8d.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-3433c8b22e8342aa.js +1 -0
- mage_ai/server/frontend_dist/block-layout.html +2 -2
- mage_ai/server/frontend_dist/compute.html +2 -2
- mage_ai/server/frontend_dist/files.html +2 -2
- mage_ai/server/frontend_dist/global-data-products/[...slug].html +2 -2
- mage_ai/server/frontend_dist/global-data-products.html +2 -2
- mage_ai/server/frontend_dist/global-hooks/[...slug].html +2 -2
- mage_ai/server/frontend_dist/global-hooks.html +2 -2
- mage_ai/server/frontend_dist/index.html +2 -2
- mage_ai/server/frontend_dist/manage/files.html +2 -2
- mage_ai/server/frontend_dist/manage/settings.html +2 -2
- mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
- mage_ai/server/frontend_dist/manage/users/new.html +2 -2
- mage_ai/server/frontend_dist/manage/users.html +2 -2
- mage_ai/server/frontend_dist/manage.html +2 -2
- mage_ai/server/frontend_dist/oauth.html +2 -2
- mage_ai/server/frontend_dist/overview.html +2 -2
- mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/dashboard.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
- mage_ai/server/frontend_dist/pipelines.html +2 -2
- mage_ai/server/frontend_dist/platform/global-hooks/[...slug].html +2 -2
- mage_ai/server/frontend_dist/platform/global-hooks.html +2 -2
- mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
- mage_ai/server/frontend_dist/settings/platform/preferences.html +2 -2
- mage_ai/server/frontend_dist/settings/platform/settings.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/permissions.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/roles.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/users/[...slug].html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
- mage_ai/server/frontend_dist/settings.html +2 -2
- mage_ai/server/frontend_dist/sign-in.html +6 -6
- mage_ai/server/frontend_dist/templates/[...slug].html +2 -2
- mage_ai/server/frontend_dist/templates.html +2 -2
- mage_ai/server/frontend_dist/terminal.html +2 -2
- mage_ai/server/frontend_dist/test.html +2 -2
- mage_ai/server/frontend_dist/triggers.html +2 -2
- mage_ai/server/frontend_dist/version-control.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/404.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/_next/static/{CKCvjsYCf2imD2X8zAOBf → TdpLLFome13qvM0gXvpHs}/_buildManifest.js +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-b3502f3f1aa92ac7.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2717-d9200be634dd6766.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-9d26185b3fb663b1.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5699-6d708c6b2153ea08.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7361-8a23dd8360593e7a.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7966-b9b85ba10667e654.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9624-8b8e100079ab69e1.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-2a69553d8c6eeb53.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-4e12783b064c1cfe.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-4bfc84ff07d7656f.js +1 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipeline-runs-a66b4c7641ae03eb.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipeline-runs-6d183f91a2ff6668.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-7181b086c93784d2.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-38e1fbcfbfc1014e.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-b645a6d13ab9fe3a.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-59aca25a5b1d3998.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-eb11c5390c982b49.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-cb88fd075a357fcf.js → triggers-4612d15a65c35912.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-3591d035bb3bb2b8.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-32985f3f7c7dd3ab.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-c2e9ef989c8bfa73.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-349af617d05f001b.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-60d01d3887e31136.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/{users-86814e581acaf5db.js → users-a4db8710f703c729.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.js +1 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/triggers-9cba3211434a8966.js → frontend_dist_base_path_template/_next/static/chunks/pages/triggers-a599c6ac89be8c8d.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-3433c8b22e8342aa.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/block-layout.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/compute.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/files.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-data-products.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-hooks/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-hooks.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/index.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/files.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/users.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/oauth.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/overview.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/platform/global-hooks/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/platform/global-hooks.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/platform/preferences.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/platform/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/sign-in.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/templates.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/terminal.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/test.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/triggers.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/version-control.html +2 -2
- mage_ai/server/scheduler_manager.py +9 -0
- mage_ai/server/server.py +47 -17
- mage_ai/server/utils/output_display.py +2 -2
- mage_ai/server/websocket_server.py +1 -0
- mage_ai/services/aws/ecs/ecs.py +1 -0
- mage_ai/services/k8s/config.py +4 -4
- mage_ai/services/k8s/utils.py +97 -0
- mage_ai/settings/keys/auth.py +2 -0
- mage_ai/settings/server.py +1 -1
- mage_ai/shared/parsers.py +6 -1
- mage_ai/streaming/sources/influxdb.py +2 -0
- mage_ai/streaming/sources/kafka.py +1 -1
- mage_ai/tests/api/endpoints/mixins.py +10 -9
- mage_ai/tests/api/endpoints/test_seeds.py +24 -0
- mage_ai/tests/api/operations/base/mixins.py +1 -1
- mage_ai/tests/api/operations/test_sessions.py +53 -2
- mage_ai/tests/api/resources/test_pipeline_resource.py +2 -2
- mage_ai/tests/authentication/oauth/test_utils.py +1 -1
- mage_ai/tests/authentication/providers/test_okta.py +43 -0
- mage_ai/tests/data_preparation/models/block/test_global_data_product.py +2 -0
- mage_ai/tests/orchestration/db/models/test_oauth.py +3 -3
- mage_ai/tests/orchestration/queue/test_process_queue.py +1 -0
- mage_ai/tests/orchestration/triggers/test_global_data_product.py +138 -136
- mage_ai/tests/server/test_server.py +27 -4
- mage_ai/tests/services/k8s/test_job_manager.py +9 -6
- mage_ai/version_control/branch/utils.py +2 -1
- mage_ai/version_control/models.py +3 -2
- {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/METADATA +5 -5
- {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/RECORD +272 -264
- mage_ai/server/frontend_dist/_next/static/chunks/1557-01f0843dc6ac4971.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/2717-b5f9575799b594d5.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/3548-961ff79ca70038c7.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/5699-6efc749f2f8ddd20.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/7361-18d9d8be96e1ce97.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/7966-f07b2913f7326b50.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/9624-59b2f803f9c88cd6.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-08790743315de36a.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/index-13760bb72d823b69.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-852d403c7bda21b3.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ff4bd7a8ec3bab40.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-bd0aff5a5ed8888c.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-d1ee961387c58b7f.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-f028ef3880ed856c.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-ceb06e1616ee9610.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-8ff16ef9566e911a.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/settings-74d76300942dcee8.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-d7a8bc51bb7a1082.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-79a4cf66a623e667.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-f59d34429fe022ee.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-5753fac7c1bfdc88.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-01f0843dc6ac4971.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2717-b5f9575799b594d5.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-961ff79ca70038c7.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5699-6efc749f2f8ddd20.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7361-18d9d8be96e1ce97.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7966-f07b2913f7326b50.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9624-59b2f803f9c88cd6.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-08790743315de36a.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-13760bb72d823b69.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-852d403c7bda21b3.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ff4bd7a8ec3bab40.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-bd0aff5a5ed8888c.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-d1ee961387c58b7f.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-f028ef3880ed856c.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-ceb06e1616ee9610.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-8ff16ef9566e911a.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-74d76300942dcee8.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-d7a8bc51bb7a1082.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-79a4cf66a623e667.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-f59d34429fe022ee.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-5753fac7c1bfdc88.js +0 -1
- /mage_ai/server/frontend_dist/_next/static/{i8pymuJDTVHdWjUP1QSh1 → RhDiJSkcjCsh4xxX4BFBk}/_ssgManifest.js +0 -0
- /mage_ai/server/frontend_dist_base_path_template/_next/static/{CKCvjsYCf2imD2X8zAOBf → TdpLLFome13qvM0gXvpHs}/_ssgManifest.js +0 -0
- {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/LICENSE +0 -0
- {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/WHEEL +0 -0
- {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/entry_points.txt +0 -0
- {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/top_level.txt +0 -0
mage_ai/io/bigquery.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import uuid
|
|
1
2
|
from typing import Dict, List, Mapping, Union
|
|
2
3
|
|
|
3
4
|
import numpy as np
|
|
@@ -14,6 +15,7 @@ from sqlglot import exp, parse_one
|
|
|
14
15
|
|
|
15
16
|
from mage_ai.io.base import QUERY_ROW_LIMIT, BaseSQLDatabase, ExportWritePolicy
|
|
16
17
|
from mage_ai.io.config import BaseConfigLoader, ConfigKey
|
|
18
|
+
from mage_ai.io.constants import UNIQUE_CONFLICT_METHOD_UPDATE
|
|
17
19
|
from mage_ai.io.export_utils import infer_dtypes
|
|
18
20
|
from mage_ai.shared.custom_logger import DX_PRINTER
|
|
19
21
|
from mage_ai.shared.environments import is_debug
|
|
@@ -200,6 +202,9 @@ WHERE TABLE_NAME = '{table_name}'
|
|
|
200
202
|
overwrite_types: Dict = None,
|
|
201
203
|
query_string: Union[str, None] = None,
|
|
202
204
|
verbose: bool = True,
|
|
205
|
+
unique_conflict_method: str = None,
|
|
206
|
+
unique_constraints: List[str] = None,
|
|
207
|
+
write_disposition: str = None,
|
|
203
208
|
**configuration_params,
|
|
204
209
|
) -> None:
|
|
205
210
|
"""
|
|
@@ -230,24 +235,23 @@ WHERE TABLE_NAME = '{table_name}'
|
|
|
230
235
|
elif type(df) is list:
|
|
231
236
|
df = DataFrame(df)
|
|
232
237
|
|
|
233
|
-
def __process(database: Union[str, None]):
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
database, schema, table_name = parts
|
|
238
|
+
def __process(database: Union[str, None], write_disposition: str = None):
|
|
239
|
+
parts = table_id.split('.')
|
|
240
|
+
if len(parts) == 2:
|
|
241
|
+
schema, table_name = parts
|
|
242
|
+
elif len(parts) == 3:
|
|
243
|
+
database, schema, table_name = parts
|
|
240
244
|
|
|
241
|
-
|
|
245
|
+
df_existing = self.client.query(f"""
|
|
242
246
|
SELECT 1
|
|
243
247
|
FROM `{database}.{schema}.__TABLES_SUMMARY__`
|
|
244
248
|
WHERE table_id = '{table_name}'
|
|
245
249
|
""").to_dataframe()
|
|
246
250
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
table_doesnt_exist = df_existing.empty
|
|
251
|
+
full_table_name = f'`{database}.{schema}.{table_name}`'
|
|
250
252
|
|
|
253
|
+
table_doesnt_exist = df_existing.empty
|
|
254
|
+
if query_string:
|
|
251
255
|
if ExportWritePolicy.FAIL == if_exists and not table_doesnt_exist:
|
|
252
256
|
raise ValueError(
|
|
253
257
|
f'Table \'{full_table_name}\' already exists in database.',
|
|
@@ -268,60 +272,129 @@ WHERE table_id = '{table_name}'
|
|
|
268
272
|
self.client.query(sql)
|
|
269
273
|
|
|
270
274
|
else:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
275
|
+
if (
|
|
276
|
+
if_exists == ExportWritePolicy.APPEND
|
|
277
|
+
and not table_doesnt_exist
|
|
278
|
+
and unique_constraints
|
|
279
|
+
and unique_conflict_method
|
|
280
|
+
):
|
|
281
|
+
temp_table_id = f'{table_id}_{uuid.uuid4().hex}'
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
self.__write_table(
|
|
285
|
+
df,
|
|
286
|
+
temp_table_id,
|
|
287
|
+
overwrite_types=overwrite_types,
|
|
288
|
+
**configuration_params,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
parts = temp_table_id.split('.')
|
|
292
|
+
if len(parts) == 2:
|
|
293
|
+
temp_table_name = parts[1]
|
|
294
|
+
elif len(parts) == 3:
|
|
295
|
+
temp_table_name = parts[2]
|
|
296
|
+
column_types = self.get_column_types(schema, temp_table_name)
|
|
297
|
+
columns = list(column_types.keys())
|
|
298
|
+
if not columns:
|
|
299
|
+
columns = df.columns.str.replace(' ', '_')
|
|
300
|
+
|
|
301
|
+
on_conditions = []
|
|
302
|
+
for col in unique_constraints:
|
|
303
|
+
on_conditions.append(
|
|
304
|
+
f'((a.{col} IS NULL AND b.{col} IS NULL) OR a.{col} = b.{col})',
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
insert_columns = ', '.join([f'`{col}`' for col in columns])
|
|
308
|
+
|
|
309
|
+
merge_commands = [
|
|
310
|
+
f'MERGE INTO `{table_id}` AS a',
|
|
311
|
+
f'USING (SELECT * FROM `{temp_table_id}`) AS b',
|
|
312
|
+
f"ON {' AND '.join(on_conditions)}",
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
if UNIQUE_CONFLICT_METHOD_UPDATE == unique_conflict_method:
|
|
316
|
+
set_command = ', '.join(
|
|
317
|
+
[f'a.`{col}` = b.`{col}`' for col in columns],
|
|
318
|
+
)
|
|
319
|
+
merge_commands.append(f'WHEN MATCHED THEN UPDATE SET {set_command}')
|
|
320
|
+
|
|
321
|
+
merge_values = f"({', '.join([f'b.`{col}`' for col in columns])})"
|
|
322
|
+
merge_commands.append(
|
|
323
|
+
f'WHEN NOT MATCHED THEN INSERT ({insert_columns}) VALUES {merge_values}', # noqa: E501
|
|
285
324
|
)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
df.loc[null_rows, col] = df.loc[null_rows, col].apply(lambda x: [{}])
|
|
308
|
-
elif col_type.startswith('ARRAY'):
|
|
309
|
-
df.loc[null_rows, col] = df.loc[null_rows, col].apply(lambda x: [])
|
|
310
|
-
elif col_type.startswith('STRUCT'):
|
|
311
|
-
df.loc[null_rows, col] = df.loc[null_rows, col].apply(lambda x: {})
|
|
312
|
-
|
|
313
|
-
# Clean column names
|
|
314
|
-
if type(df) is DataFrame:
|
|
315
|
-
df.columns = df.columns.str.replace(' ', '_')
|
|
316
|
-
|
|
317
|
-
self.client.load_table_from_dataframe(df, table_id, job_config=config).result()
|
|
325
|
+
|
|
326
|
+
merge_command = '\n'.join(merge_commands)
|
|
327
|
+
|
|
328
|
+
self.client.query(merge_command).result()
|
|
329
|
+
finally:
|
|
330
|
+
self.client.query(f'DROP TABLE IF EXISTS {temp_table_id}').result()
|
|
331
|
+
else:
|
|
332
|
+
if not write_disposition:
|
|
333
|
+
if if_exists == ExportWritePolicy.APPEND:
|
|
334
|
+
write_disposition = WriteDisposition.WRITE_APPEND
|
|
335
|
+
elif if_exists == ExportWritePolicy.REPLACE:
|
|
336
|
+
write_disposition = WriteDisposition.WRITE_TRUNCATE
|
|
337
|
+
elif if_exists == ExportWritePolicy.FAIL:
|
|
338
|
+
write_disposition = WriteDisposition.WRITE_EMPTY
|
|
339
|
+
self.__write_table(
|
|
340
|
+
df,
|
|
341
|
+
table_id,
|
|
342
|
+
overwrite_types=overwrite_types,
|
|
343
|
+
write_disposition=write_disposition,
|
|
344
|
+
**configuration_params,
|
|
345
|
+
)
|
|
318
346
|
|
|
319
347
|
if verbose:
|
|
320
348
|
with self.printer.print_msg(f'Exporting data to table \'{table_id}\''):
|
|
321
|
-
__process(database=database)
|
|
349
|
+
__process(database=database, write_disposition=write_disposition)
|
|
322
350
|
else:
|
|
323
351
|
__process(database=database)
|
|
324
352
|
|
|
353
|
+
def __write_table(
|
|
354
|
+
self,
|
|
355
|
+
df: DataFrame,
|
|
356
|
+
table_id: str,
|
|
357
|
+
overwrite_types: Dict = None,
|
|
358
|
+
**configuration_params,
|
|
359
|
+
):
|
|
360
|
+
config = LoadJobConfig(**configuration_params)
|
|
361
|
+
if overwrite_types is not None:
|
|
362
|
+
config.schema = [SchemaField(k, v) for k, v in overwrite_types.items()]
|
|
363
|
+
if not config.write_disposition:
|
|
364
|
+
config.write_disposition = WriteDisposition.WRITE_APPEND
|
|
365
|
+
parts = table_id.split('.')
|
|
366
|
+
if len(parts) == 2:
|
|
367
|
+
schema = parts[0]
|
|
368
|
+
table_name = parts[1]
|
|
369
|
+
elif len(parts) == 3:
|
|
370
|
+
schema = parts[1]
|
|
371
|
+
table_name = parts[2]
|
|
372
|
+
|
|
373
|
+
self.client.create_dataset(dataset=schema, exists_ok=True)
|
|
374
|
+
|
|
375
|
+
column_types = self.get_column_types(schema, table_name)
|
|
376
|
+
|
|
377
|
+
if df is not None:
|
|
378
|
+
df.fillna(value=np.NaN, inplace=True)
|
|
379
|
+
for col in df.columns:
|
|
380
|
+
col_type = column_types.get(col)
|
|
381
|
+
if not col_type:
|
|
382
|
+
continue
|
|
383
|
+
|
|
384
|
+
null_rows = df[col].isnull()
|
|
385
|
+
if col_type.startswith('ARRAY<STRUCT'):
|
|
386
|
+
df.loc[null_rows, col] = df.loc[null_rows, col].apply(lambda x: [{}])
|
|
387
|
+
elif col_type.startswith('ARRAY'):
|
|
388
|
+
df.loc[null_rows, col] = df.loc[null_rows, col].apply(lambda x: [])
|
|
389
|
+
elif col_type.startswith('STRUCT'):
|
|
390
|
+
df.loc[null_rows, col] = df.loc[null_rows, col].apply(lambda x: {})
|
|
391
|
+
|
|
392
|
+
# Clean column names
|
|
393
|
+
if type(df) is DataFrame:
|
|
394
|
+
df.columns = df.columns.str.replace(' ', '_')
|
|
395
|
+
|
|
396
|
+
return self.client.load_table_from_dataframe(df, table_id, job_config=config).result()
|
|
397
|
+
|
|
325
398
|
def execute(self, query_string: str, **kwargs) -> None:
|
|
326
399
|
"""
|
|
327
400
|
Sends query to the connected BigQuery warehouse.
|
mage_ai/io/mysql.py
CHANGED
|
@@ -8,6 +8,7 @@ from mysql.connector.cursor import MySQLCursor
|
|
|
8
8
|
from pandas import DataFrame, Series
|
|
9
9
|
|
|
10
10
|
from mage_ai.io.config import BaseConfigLoader, ConfigKey
|
|
11
|
+
from mage_ai.io.constants import UNIQUE_CONFLICT_METHOD_UPDATE
|
|
11
12
|
from mage_ai.io.export_utils import PandasTypes
|
|
12
13
|
from mage_ai.io.sql import BaseSQL
|
|
13
14
|
from mage_ai.shared.parsers import encode_complex
|
|
@@ -60,7 +61,7 @@ class MySQL(BaseSQL):
|
|
|
60
61
|
) -> str:
|
|
61
62
|
if unique_constraints is None:
|
|
62
63
|
unique_constraints = []
|
|
63
|
-
|
|
64
|
+
columns_and_types = []
|
|
64
65
|
for cname in dtypes:
|
|
65
66
|
if overwrite_types is not None and cname in overwrite_types.keys():
|
|
66
67
|
dtypes[cname] = overwrite_types[cname]
|
|
@@ -68,9 +69,22 @@ class MySQL(BaseSQL):
|
|
|
68
69
|
cleaned_col_name = clean_name(cname, case_sensitive=case_sensitive)
|
|
69
70
|
else:
|
|
70
71
|
cleaned_col_name = cname
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
columns_and_types.append(f'`{cleaned_col_name}` {dtypes[cname]} NULL')
|
|
73
|
+
|
|
74
|
+
if unique_constraints:
|
|
75
|
+
unique_constraints = [
|
|
76
|
+
clean_name(col, case_sensitive=case_sensitive)
|
|
77
|
+
for col in unique_constraints
|
|
78
|
+
]
|
|
79
|
+
index_name = '_'.join([
|
|
80
|
+
clean_name(table_name, case_sensitive=case_sensitive),
|
|
81
|
+
] + unique_constraints)
|
|
82
|
+
index_name = f'unique{index_name}'[:64]
|
|
83
|
+
columns_and_types.append(
|
|
84
|
+
f"CONSTRAINT {index_name} Unique({', '.join(unique_constraints)})"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return f'CREATE TABLE {table_name} (' + ','.join(columns_and_types) + ');'
|
|
74
88
|
|
|
75
89
|
def open(self) -> None:
|
|
76
90
|
with self.printer.print_msg('Opening connection to MySQL database'):
|
|
@@ -94,6 +108,9 @@ class MySQL(BaseSQL):
|
|
|
94
108
|
dtypes: List[str],
|
|
95
109
|
full_table_name: str,
|
|
96
110
|
buffer: Union[IO, None] = None,
|
|
111
|
+
case_sensitive: bool = False,
|
|
112
|
+
unique_constraints: List[str] = None,
|
|
113
|
+
unique_conflict_method: str = None,
|
|
97
114
|
**kwargs,
|
|
98
115
|
) -> None:
|
|
99
116
|
def serialize_obj(val):
|
|
@@ -133,9 +150,24 @@ class MySQL(BaseSQL):
|
|
|
133
150
|
for _, row in df_.iterrows():
|
|
134
151
|
values.append(tuple([str(val) if type(val) is pd.Timestamp else val for val in row]))
|
|
135
152
|
|
|
136
|
-
|
|
153
|
+
cleaned_columns = [clean_name(col, case_sensitive=case_sensitive) for col in columns]
|
|
154
|
+
insert_columns = ', '.join([f'`{col}`'for col in cleaned_columns])
|
|
155
|
+
|
|
156
|
+
query = [
|
|
157
|
+
f'INSERT INTO {full_table_name} ({insert_columns})',
|
|
158
|
+
f'VALUES ({values_placeholder})',
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
if unique_constraints and unique_conflict_method:
|
|
162
|
+
if UNIQUE_CONFLICT_METHOD_UPDATE == unique_conflict_method:
|
|
163
|
+
update_command = [f'{col} = new.{col}' for col in cleaned_columns]
|
|
164
|
+
query += [
|
|
165
|
+
'AS new',
|
|
166
|
+
f"ON DUPLICATE KEY UPDATE {', '.join(update_command)}",
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
sql = '\n'.join(query)
|
|
137
170
|
|
|
138
|
-
sql = f'INSERT INTO {full_table_name} ({insert_columns}) VALUES ({values_placeholder})'
|
|
139
171
|
cursor.executemany(sql, values)
|
|
140
172
|
|
|
141
173
|
def get_type(self, column: Series, dtype: str) -> str:
|
mage_ai/io/snowflake.py
CHANGED
|
@@ -11,6 +11,7 @@ from mage_ai.data_preparation.models.block.sql.utils.shared import (
|
|
|
11
11
|
)
|
|
12
12
|
from mage_ai.io.base import QUERY_ROW_LIMIT, BaseSQLConnection, ExportWritePolicy
|
|
13
13
|
from mage_ai.io.config import BaseConfigLoader, ConfigKey
|
|
14
|
+
from mage_ai.io.constants import UNIQUE_CONFLICT_METHOD_UPDATE
|
|
14
15
|
from mage_ai.shared.hash import merge_dict
|
|
15
16
|
|
|
16
17
|
DEFAULT_LOGIN_TIMEOUT = 20
|
|
@@ -201,6 +202,8 @@ class Snowflake(BaseSQLConnection):
|
|
|
201
202
|
schema: str = None,
|
|
202
203
|
if_exists: str = 'append',
|
|
203
204
|
query_string: Union[str, None] = None,
|
|
205
|
+
unique_conflict_method: str = None,
|
|
206
|
+
unique_constraints: List[str] = None,
|
|
204
207
|
verbose: bool = True,
|
|
205
208
|
**kwargs,
|
|
206
209
|
) -> None:
|
|
@@ -265,37 +268,28 @@ class Snowflake(BaseSQLConnection):
|
|
|
265
268
|
cur.execute(f'DROP TABLE "{schema}"."{table_name}"', timeout=self.timeout)
|
|
266
269
|
should_create_table = True
|
|
267
270
|
|
|
268
|
-
if
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if should_create_table:
|
|
273
|
-
cur.execute(f"""
|
|
274
|
-
CREATE TABLE IF NOT EXISTS "{database}"."{schema}"."{table_name}" AS
|
|
275
|
-
{query_string}
|
|
276
|
-
""", timeout=self.timeout)
|
|
277
|
-
else:
|
|
278
|
-
cur.execute(f"""
|
|
279
|
-
INSERT INTO "{database}"."{schema}"."{table_name}"
|
|
280
|
-
{query_string}
|
|
281
|
-
""", timeout=self.timeout)
|
|
282
|
-
|
|
283
|
-
else:
|
|
284
|
-
write_kwargs = merge_dict(
|
|
285
|
-
dict(
|
|
286
|
-
auto_create_table=should_create_table,
|
|
287
|
-
database=database,
|
|
288
|
-
schema=schema,
|
|
289
|
-
# This param makes sure datetime column is written correctly
|
|
290
|
-
use_logical_type=True,
|
|
291
|
-
),
|
|
292
|
-
kwargs or dict(),
|
|
293
|
-
)
|
|
294
|
-
write_pandas(
|
|
295
|
-
self.conn,
|
|
271
|
+
if unique_constraints and unique_conflict_method and df is not None:
|
|
272
|
+
self.__upsert_df_into_table(
|
|
273
|
+
table_name,
|
|
296
274
|
df,
|
|
275
|
+
cur,
|
|
276
|
+
database,
|
|
277
|
+
schema,
|
|
278
|
+
should_create_table=should_create_table,
|
|
279
|
+
unique_conflict_method=unique_conflict_method,
|
|
280
|
+
unique_constraints=unique_constraints,
|
|
281
|
+
**kwargs,
|
|
282
|
+
)
|
|
283
|
+
else:
|
|
284
|
+
self.__write_table(
|
|
297
285
|
table_name,
|
|
298
|
-
|
|
286
|
+
df,
|
|
287
|
+
cur,
|
|
288
|
+
database,
|
|
289
|
+
schema,
|
|
290
|
+
should_create_table=should_create_table,
|
|
291
|
+
query_string=query_string,
|
|
292
|
+
**kwargs,
|
|
299
293
|
)
|
|
300
294
|
|
|
301
295
|
if verbose:
|
|
@@ -306,6 +300,135 @@ INSERT INTO "{database}"."{schema}"."{table_name}"
|
|
|
306
300
|
else:
|
|
307
301
|
__process()
|
|
308
302
|
|
|
303
|
+
def __upsert_df_into_table(
|
|
304
|
+
self,
|
|
305
|
+
table_name: str,
|
|
306
|
+
df: DataFrame,
|
|
307
|
+
cursor,
|
|
308
|
+
database: str,
|
|
309
|
+
schema: str,
|
|
310
|
+
should_create_table: bool = False,
|
|
311
|
+
unique_conflict_method: str = None,
|
|
312
|
+
unique_constraints: List[str] = None,
|
|
313
|
+
allow_reserved_words: bool = True,
|
|
314
|
+
auto_clean_name: bool = True,
|
|
315
|
+
case_sensitive: bool = True,
|
|
316
|
+
**kwargs
|
|
317
|
+
):
|
|
318
|
+
write_kwargs = merge_dict(
|
|
319
|
+
dict(
|
|
320
|
+
auto_create_table=True,
|
|
321
|
+
database=database,
|
|
322
|
+
schema=schema,
|
|
323
|
+
# This param makes sure datetime column is written correctly
|
|
324
|
+
use_logical_type=True,
|
|
325
|
+
),
|
|
326
|
+
kwargs or dict(),
|
|
327
|
+
)
|
|
328
|
+
# should_create_table is True when the table does not exist, so just create the
|
|
329
|
+
# table as normal.
|
|
330
|
+
if should_create_table:
|
|
331
|
+
write_pandas(
|
|
332
|
+
self.conn,
|
|
333
|
+
df,
|
|
334
|
+
table_name,
|
|
335
|
+
**write_kwargs,
|
|
336
|
+
)
|
|
337
|
+
else:
|
|
338
|
+
temp_table_name = f'temp_{table_name}'
|
|
339
|
+
write_pandas(
|
|
340
|
+
self.conn,
|
|
341
|
+
df,
|
|
342
|
+
temp_table_name,
|
|
343
|
+
table_type='temp',
|
|
344
|
+
**write_kwargs,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
cleaned_unique_constraints = []
|
|
348
|
+
for col in unique_constraints:
|
|
349
|
+
cleaned_col = self._clean_column_name(
|
|
350
|
+
col,
|
|
351
|
+
allow_reserved_words=allow_reserved_words,
|
|
352
|
+
auto_clean_name=auto_clean_name,
|
|
353
|
+
case_sensitive=case_sensitive,
|
|
354
|
+
)
|
|
355
|
+
cleaned_unique_constraints.append(f'"{cleaned_col}"')
|
|
356
|
+
|
|
357
|
+
cleaned_columns = []
|
|
358
|
+
for col in df.columns:
|
|
359
|
+
cleaned_col = self._clean_column_name(
|
|
360
|
+
col,
|
|
361
|
+
allow_reserved_words=allow_reserved_words,
|
|
362
|
+
auto_clean_name=auto_clean_name,
|
|
363
|
+
case_sensitive=case_sensitive,
|
|
364
|
+
)
|
|
365
|
+
cleaned_columns.append(f'"{cleaned_col}"')
|
|
366
|
+
|
|
367
|
+
merge_commands = [
|
|
368
|
+
f'MERGE INTO "{database}"."{schema}"."{table_name}" AS a',
|
|
369
|
+
f'USING (SELECT * FROM "{database}"."{schema}"."{temp_table_name}") AS b',
|
|
370
|
+
f"ON {' AND '.join([f'a.{col} = b.{col}' for col in cleaned_unique_constraints])}",
|
|
371
|
+
]
|
|
372
|
+
|
|
373
|
+
if unique_conflict_method == UNIQUE_CONFLICT_METHOD_UPDATE:
|
|
374
|
+
set_command = ', '.join(
|
|
375
|
+
[f'a.{col} = b.{col}' for col in cleaned_columns],
|
|
376
|
+
)
|
|
377
|
+
merge_commands.append(f'WHEN MATCHED THEN UPDATE SET {set_command}')
|
|
378
|
+
|
|
379
|
+
insert_columns = ', '.join(cleaned_columns)
|
|
380
|
+
merge_values = f"({', '.join([f'b.{col}' for col in cleaned_columns])})"
|
|
381
|
+
merge_commands.append(
|
|
382
|
+
f"WHEN NOT MATCHED THEN INSERT ({insert_columns}) VALUES {merge_values}",
|
|
383
|
+
)
|
|
384
|
+
merge_command = '\n'.join(merge_commands)
|
|
385
|
+
|
|
386
|
+
cursor.execute(merge_command, timeout=self.timeout)
|
|
387
|
+
|
|
388
|
+
def __write_table(
|
|
389
|
+
self,
|
|
390
|
+
table_name: str,
|
|
391
|
+
df: DataFrame,
|
|
392
|
+
cursor,
|
|
393
|
+
database: str,
|
|
394
|
+
schema: str,
|
|
395
|
+
should_create_table: bool = False,
|
|
396
|
+
query_string: str = None,
|
|
397
|
+
**kwargs
|
|
398
|
+
):
|
|
399
|
+
if query_string:
|
|
400
|
+
cursor.execute(f'USE DATABASE {database}', timeout=self.timeout)
|
|
401
|
+
cursor.execute(f'USE SCHEMA {schema}', timeout=self.timeout)
|
|
402
|
+
|
|
403
|
+
if should_create_table:
|
|
404
|
+
cursor.execute(f"""
|
|
405
|
+
CREATE TABLE IF NOT EXISTS "{database}"."{schema}"."{table_name}" AS
|
|
406
|
+
{query_string}
|
|
407
|
+
""", timeout=self.timeout)
|
|
408
|
+
else:
|
|
409
|
+
cursor.execute(f"""
|
|
410
|
+
INSERT INTO "{database}"."{schema}"."{table_name}"
|
|
411
|
+
{query_string}
|
|
412
|
+
""", timeout=self.timeout)
|
|
413
|
+
|
|
414
|
+
else:
|
|
415
|
+
write_kwargs = merge_dict(
|
|
416
|
+
dict(
|
|
417
|
+
auto_create_table=should_create_table,
|
|
418
|
+
database=database,
|
|
419
|
+
schema=schema,
|
|
420
|
+
# This param makes sure datetime column is written correctly
|
|
421
|
+
use_logical_type=True,
|
|
422
|
+
),
|
|
423
|
+
kwargs or dict(),
|
|
424
|
+
)
|
|
425
|
+
write_pandas(
|
|
426
|
+
self.conn,
|
|
427
|
+
df,
|
|
428
|
+
table_name,
|
|
429
|
+
**write_kwargs,
|
|
430
|
+
)
|
|
431
|
+
|
|
309
432
|
@classmethod
|
|
310
433
|
def with_config(
|
|
311
434
|
cls,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Update token column type
|
|
2
|
+
|
|
3
|
+
Revision ID: 42a14d6143f1
|
|
4
|
+
Revises: b9a2d6d0a2c7
|
|
5
|
+
Create Date: 2024-04-12 15:19:52.639580
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from alembic import op
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# revision identifiers, used by Alembic.
|
|
13
|
+
revision = '42a14d6143f1'
|
|
14
|
+
down_revision = 'b9a2d6d0a2c7'
|
|
15
|
+
branch_labels = None
|
|
16
|
+
depends_on = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
21
|
+
with op.batch_alter_table('oauth2_access_token', schema=None) as batch_op:
|
|
22
|
+
batch_op.alter_column(
|
|
23
|
+
'token',
|
|
24
|
+
existing_type=sa.String(length=255),
|
|
25
|
+
type_=sa.Text(),
|
|
26
|
+
existing_nullable=True,
|
|
27
|
+
)
|
|
28
|
+
batch_op.alter_column(
|
|
29
|
+
'refresh_token',
|
|
30
|
+
existing_type=sa.String(length=255),
|
|
31
|
+
type_=sa.Text(),
|
|
32
|
+
existing_nullable=True,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# ### end Alembic commands ###
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def downgrade() -> None:
|
|
39
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
40
|
+
with op.batch_alter_table('oauth2_access_token', schema=None) as batch_op:
|
|
41
|
+
batch_op.alter_column(
|
|
42
|
+
'token',
|
|
43
|
+
existing_type=sa.Text(),
|
|
44
|
+
type_=sa.String(length=255),
|
|
45
|
+
existing_nullable=True,
|
|
46
|
+
)
|
|
47
|
+
batch_op.alter_column(
|
|
48
|
+
'refresh_token',
|
|
49
|
+
existing_type=sa.String(length=255),
|
|
50
|
+
type_=sa.Text(),
|
|
51
|
+
existing_nullable=True,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# ### end Alembic commands ###
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import enum
|
|
2
2
|
import re
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import Dict, List, Union
|
|
4
|
+
from typing import Callable, Dict, List, Union
|
|
5
5
|
|
|
6
6
|
from sqlalchemy import (
|
|
7
7
|
JSON,
|
|
@@ -12,6 +12,7 @@ from sqlalchemy import (
|
|
|
12
12
|
ForeignKey,
|
|
13
13
|
Integer,
|
|
14
14
|
String,
|
|
15
|
+
Text,
|
|
15
16
|
and_,
|
|
16
17
|
asc,
|
|
17
18
|
func,
|
|
@@ -324,10 +325,10 @@ class Role(BaseModel):
|
|
|
324
325
|
@classmethod
|
|
325
326
|
@safe_db_query
|
|
326
327
|
def create_default_roles(
|
|
327
|
-
|
|
328
|
+
cls,
|
|
328
329
|
entity: Entity = None,
|
|
329
330
|
entity_id: str = None,
|
|
330
|
-
|
|
331
|
+
name_func: Callable[[str], str] = None,
|
|
331
332
|
) -> None:
|
|
332
333
|
"""
|
|
333
334
|
Create default roles with associated permissions for a given entity and entity_id.
|
|
@@ -342,18 +343,18 @@ class Role(BaseModel):
|
|
|
342
343
|
entity = Entity.GLOBAL
|
|
343
344
|
permissions = Permission.create_default_permissions(entity=entity, entity_id=entity_id)
|
|
344
345
|
mapping = {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
346
|
+
cls.DefaultRole.OWNER: Permission.Access.OWNER,
|
|
347
|
+
cls.DefaultRole.ADMIN: Permission.Access.ADMIN,
|
|
348
|
+
cls.DefaultRole.EDITOR: Permission.Access.EDITOR,
|
|
349
|
+
cls.DefaultRole.VIEWER: Permission.Access.VIEWER,
|
|
349
350
|
}
|
|
350
351
|
for name, access in mapping.items():
|
|
351
352
|
role_name = name
|
|
352
|
-
if
|
|
353
|
-
role_name =
|
|
354
|
-
role =
|
|
353
|
+
if name_func is not None:
|
|
354
|
+
role_name = name_func(name)
|
|
355
|
+
role = cls.query.filter(Role.name == role_name).first()
|
|
355
356
|
if not role:
|
|
356
|
-
|
|
357
|
+
cls.create(
|
|
357
358
|
name=role_name,
|
|
358
359
|
permissions=[
|
|
359
360
|
Permission.query.filter(
|
|
@@ -880,10 +881,10 @@ class Oauth2AccessToken(BaseModel):
|
|
|
880
881
|
expires = Column(DateTime(timezone=True))
|
|
881
882
|
oauth2_application = relationship(Oauth2Application, back_populates='oauth2_access_tokens')
|
|
882
883
|
oauth2_application_id = Column(Integer, ForeignKey('oauth2_application.id'))
|
|
883
|
-
token = Column(
|
|
884
|
+
token = Column(Text, index=True, unique=True)
|
|
884
885
|
user = relationship(User, back_populates='oauth2_access_tokens')
|
|
885
886
|
user_id = Column(Integer, ForeignKey('user.id'))
|
|
886
|
-
refresh_token = Column(
|
|
887
|
+
refresh_token = Column(Text)
|
|
887
888
|
|
|
888
889
|
def is_valid(self) -> bool:
|
|
889
890
|
return self.token and \
|