mage-ai 0.9.67__py3-none-any.whl → 0.9.69__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/monitors/BaseMonitor.py +1 -2
- mage_ai/api/policies/PipelinePolicy.py +2 -0
- mage_ai/api/resources/BlockLayoutItemResource.py +2 -2
- mage_ai/api/resources/BlockResource.py +4 -3
- mage_ai/api/resources/CacheItemResource.py +1 -1
- mage_ai/api/resources/GitBranchResource.py +3 -5
- mage_ai/api/resources/PageBlockLayoutResource.py +2 -2
- mage_ai/api/resources/PipelineResource.py +13 -0
- mage_ai/api/resources/PipelineRunResource.py +10 -1
- mage_ai/api/resources/SeedResource.py +2 -1
- mage_ai/api/resources/SessionResource.py +13 -1
- 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/tag.py +3 -0
- mage_ai/cluster_manager/manage.py +4 -1
- mage_ai/data_preparation/executors/k8s_block_executor.py +8 -1
- mage_ai/data_preparation/executors/k8s_pipeline_executor.py +12 -1
- mage_ai/data_preparation/executors/streaming_pipeline_executor.py +77 -7
- mage_ai/data_preparation/logging/gcs_logger_manager.py +7 -4
- mage_ai/data_preparation/models/block/__init__.py +28 -71
- mage_ai/data_preparation/models/block/block_factory.py +77 -0
- mage_ai/data_preparation/models/block/data_integration/mixins.py +16 -5
- mage_ai/data_preparation/models/block/dbt/block.py +5 -7
- mage_ai/data_preparation/models/block/dynamic/child.py +3 -0
- 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 +9 -3
- mage_ai/data_preparation/models/block/integration/__init__.py +13 -9
- mage_ai/data_preparation/models/block/platform/mixins.py +1 -1
- mage_ai/data_preparation/models/block/sql/__init__.py +1 -1
- mage_ai/data_preparation/models/pipeline.py +102 -19
- 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/gcs_storage.py +1 -1
- mage_ai/data_preparation/templates/constants.py +7 -0
- 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/export_utils.py +3 -0
- mage_ai/io/mysql.py +38 -6
- mage_ai/io/oracledb.py +138 -3
- mage_ai/io/snowflake.py +152 -29
- mage_ai/io/sql.py +4 -0
- mage_ai/orchestration/db/__init__.py +2 -2
- mage_ai/orchestration/db/models/oauth.py +4 -4
- mage_ai/orchestration/db/models/schedules.py +10 -3
- mage_ai/orchestration/job_manager.py +6 -0
- mage_ai/orchestration/notification/sender.py +8 -0
- mage_ai/orchestration/pipeline_scheduler_original.py +26 -7
- 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/server/constants.py +1 -1
- mage_ai/server/frontend_dist/404.html +2 -2
- mage_ai/server/{frontend_dist_base_path_template/_next/static/khKiaJtwrslgMmp4YSa1f → frontend_dist/_next/static/_krrrgup_C-dPOpX36S8I}/_buildManifest.js +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/1557-df144fbd8b2208c3.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-fa0792ddb88f4646.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/4241-ccd0126f5750cc35.js → frontend_dist/_next/static/chunks/4241-4499461184ba0d23.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/5627-237a3de578538022.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_base_path_template/_next/static/chunks/7966-5c1786fb7e7a48f5.js → frontend_dist/_next/static/chunks/7966-f07b2913f7326b50.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-d9c89527266296f7.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/users/[user]-8bbfa0c19b5e4cb3.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-852d403c7bda21b3.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/overview-597b74828bf105db.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{pipeline-runs-a66b4c7641ae03eb.js → pipeline-runs-3edc6270c5b0e962.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-e1dd1ed71d26c10d.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1417ad1c821d720a.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]/syncs-90abafc7ed61c582.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-1bdfda8edc9cf4a8.js +1 -0
- 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-503049734a8b082f.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-5b26eeda8aed8a7b.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-79a4cf66a623e667.js → frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-8b793b3b696a2cd3.js} +1 -1
- mage_ai/server/frontend_dist/_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-7d38b2f7c3e918a1.js → frontend_dist/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-9cba3211434a8966.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 +3 -3
- 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 +5 -5
- 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 +2 -2
- mage_ai/server/{frontend_dist/_next/static/vPsMu6Fi2zrHaf2fRXKRO → frontend_dist_base_path_template/_next/static/KLL5mirre9d7_ZeEpaw3s}/_buildManifest.js +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-df144fbd8b2208c3.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-fa0792ddb88f4646.js +1 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/4241-ccd0126f5750cc35.js → frontend_dist_base_path_template/_next/static/chunks/4241-4499461184ba0d23.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5627-237a3de578538022.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/_next/static/chunks/7966-5c1786fb7e7a48f5.js → frontend_dist_base_path_template/_next/static/chunks/7966-f07b2913f7326b50.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-d9c89527266296f7.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/users/[user]-8bbfa0c19b5e4cb3.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-852d403c7bda21b3.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/overview-597b74828bf105db.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{pipeline-runs-a66b4c7641ae03eb.js → pipeline-runs-3edc6270c5b0e962.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-e1dd1ed71d26c10d.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1417ad1c821d720a.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]/syncs-90abafc7ed61c582.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-1bdfda8edc9cf4a8.js +1 -0
- 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-503049734a8b082f.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-5b26eeda8aed8a7b.js +1 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-79a4cf66a623e667.js → frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-8b793b3b696a2cd3.js} +1 -1
- 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/_next/static/chunks/pages/sign-in-7d38b2f7c3e918a1.js → frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-9cba3211434a8966.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 +3 -3
- 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 +5 -5
- 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 +7 -0
- mage_ai/server/server.py +12 -5
- mage_ai/server/websocket_server.py +1 -0
- mage_ai/services/k8s/job_manager.py +8 -0
- mage_ai/services/slack/slack.py +10 -1
- mage_ai/services/spark/spark.py +9 -2
- mage_ai/settings/backends.py +8 -8
- mage_ai/settings/keys/auth.py +2 -0
- mage_ai/settings/models/configuration_option.py +10 -9
- mage_ai/settings/server.py +1 -1
- mage_ai/shared/files.py +19 -1
- mage_ai/shared/path_fixer.py +3 -0
- mage_ai/streaming/sources/base.py +5 -0
- mage_ai/streaming/sources/influxdb.py +2 -0
- mage_ai/streaming/sources/kafka.py +2 -1
- mage_ai/streaming/sources/mongodb.py +4 -0
- mage_ai/tests/api/endpoints/mixins.py +10 -9
- mage_ai/tests/api/endpoints/test_seeds.py +24 -0
- mage_ai/tests/api/operations/test_sessions.py +53 -2
- mage_ai/tests/authentication/providers/test_okta.py +43 -0
- mage_ai/tests/data_preparation/models/block/dbt/test_block.py +2 -2
- mage_ai/tests/data_preparation/models/block/dbt/test_block_sql.py +1 -1
- mage_ai/tests/data_preparation/models/block/dbt/test_block_yaml.py +1 -1
- mage_ai/tests/data_preparation/models/test_block.py +2 -1
- 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/test_pipeline_scheduler.py +2 -0
- mage_ai/tests/server/test_server.py +8 -4
- mage_ai/tests/settings/models/test_configuration_option.py +2 -2
- {mage_ai-0.9.67.dist-info → mage_ai-0.9.69.dist-info}/METADATA +6 -6
- {mage_ai-0.9.67.dist-info → mage_ai-0.9.69.dist-info}/RECORD +263 -259
- mage_ai/server/frontend_dist/_next/static/chunks/1557-01f0843dc6ac4971.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/181-e61915415a976861.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-13563a1ff815f922.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/pages/_app-1c1ffd928f5a00f7.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/index-b7b8695a7f9efde2.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/[user]-d3a5fd3119fdb1e4.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-42789d698d28a92f.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/overview-d05040edba41b2ac.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-aaf393c86fc1bda3.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-36377e679da2cd91.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1b688d61f8efe07a.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-ed3331d22d5cff7d.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/syncs-d94e48bad89ba1e0.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-f508c2f261297724.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-f99e99aa8f45529c.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-6826000cdffc36b8.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-dde29a463495cebb.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-ab98a7b3a678669e.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/181-e61915415a976861.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-13563a1ff815f922.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/pages/_app-1c1ffd928f5a00f7.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-b7b8695a7f9efde2.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/[user]-d3a5fd3119fdb1e4.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-42789d698d28a92f.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/overview-d05040edba41b2ac.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-aaf393c86fc1bda3.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-36377e679da2cd91.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1b688d61f8efe07a.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-ed3331d22d5cff7d.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/syncs-d94e48bad89ba1e0.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-f508c2f261297724.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-f99e99aa8f45529c.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-6826000cdffc36b8.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-dde29a463495cebb.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-ab98a7b3a678669e.js +0 -1
- /mage_ai/server/frontend_dist/_next/static/{vPsMu6Fi2zrHaf2fRXKRO → _krrrgup_C-dPOpX36S8I}/_ssgManifest.js +0 -0
- /mage_ai/server/frontend_dist_base_path_template/_next/static/{khKiaJtwrslgMmp4YSa1f → KLL5mirre9d7_ZeEpaw3s}/_ssgManifest.js +0 -0
- {mage_ai-0.9.67.dist-info → mage_ai-0.9.69.dist-info}/LICENSE +0 -0
- {mage_ai-0.9.67.dist-info → mage_ai-0.9.69.dist-info}/WHEEL +0 -0
- {mage_ai-0.9.67.dist-info → mage_ai-0.9.69.dist-info}/entry_points.txt +0 -0
- {mage_ai-0.9.67.dist-info → mage_ai-0.9.69.dist-info}/top_level.txt +0 -0
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,
|
mage_ai/io/sql.py
CHANGED
|
@@ -56,6 +56,7 @@ class BaseSQL(BaseSQLConnection):
|
|
|
56
56
|
case_sensitive: bool = False,
|
|
57
57
|
unique_constraints: List[str] = None,
|
|
58
58
|
overwrite_types: Dict = None,
|
|
59
|
+
skip_semicolon_at_end: bool = False,
|
|
59
60
|
**kwargs,
|
|
60
61
|
) -> str:
|
|
61
62
|
if unique_constraints is None:
|
|
@@ -68,6 +69,7 @@ class BaseSQL(BaseSQLConnection):
|
|
|
68
69
|
case_sensitive=case_sensitive,
|
|
69
70
|
unique_constraints=unique_constraints,
|
|
70
71
|
overwrite_types=overwrite_types,
|
|
72
|
+
skip_semicolon_at_end=skip_semicolon_at_end,
|
|
71
73
|
)
|
|
72
74
|
|
|
73
75
|
def build_create_table_as_command(
|
|
@@ -234,6 +236,7 @@ class BaseSQL(BaseSQLConnection):
|
|
|
234
236
|
query_string: Union[str, None] = None,
|
|
235
237
|
unique_conflict_method: str = None,
|
|
236
238
|
unique_constraints: List[str] = None,
|
|
239
|
+
skip_semicolon_at_end: bool = False,
|
|
237
240
|
**kwargs,
|
|
238
241
|
) -> None:
|
|
239
242
|
"""
|
|
@@ -350,6 +353,7 @@ class BaseSQL(BaseSQLConnection):
|
|
|
350
353
|
case_sensitive=case_sensitive,
|
|
351
354
|
unique_constraints=unique_constraints,
|
|
352
355
|
overwrite_types=overwrite_types,
|
|
356
|
+
skip_semicolon_at_end=skip_semicolon_at_end,
|
|
353
357
|
)
|
|
354
358
|
cur.execute(query)
|
|
355
359
|
self.upload_dataframe(
|
|
@@ -13,7 +13,7 @@ from mage_ai.orchestration.db.setup import get_postgres_connection_url
|
|
|
13
13
|
from mage_ai.orchestration.db.utils import get_user_info_from_db_connection_url
|
|
14
14
|
from mage_ai.settings import OTEL_EXPORTER_OTLP_ENDPOINT
|
|
15
15
|
from mage_ai.settings.repo import get_variables_dir
|
|
16
|
-
from mage_ai.shared.environments import
|
|
16
|
+
from mage_ai.shared.environments import is_debug, is_test
|
|
17
17
|
|
|
18
18
|
DB_RETRY_COUNT = 2
|
|
19
19
|
TEST_DB = 'test.db'
|
|
@@ -170,5 +170,5 @@ def safe_db_query(func):
|
|
|
170
170
|
|
|
171
171
|
logging.basicConfig()
|
|
172
172
|
|
|
173
|
-
if
|
|
173
|
+
if is_debug() and not os.getenv('DISABLE_DATABASE_TERMINAL_OUTPUT'):
|
|
174
174
|
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
|
|
@@ -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,
|
|
@@ -327,7 +327,7 @@ class Role(BaseModel):
|
|
|
327
327
|
self,
|
|
328
328
|
entity: Entity = None,
|
|
329
329
|
entity_id: str = None,
|
|
330
|
-
|
|
330
|
+
name_func: Callable[[str], str] = None,
|
|
331
331
|
) -> None:
|
|
332
332
|
"""
|
|
333
333
|
Create default roles with associated permissions for a given entity and entity_id.
|
|
@@ -349,8 +349,8 @@ class Role(BaseModel):
|
|
|
349
349
|
}
|
|
350
350
|
for name, access in mapping.items():
|
|
351
351
|
role_name = name
|
|
352
|
-
if
|
|
353
|
-
role_name =
|
|
352
|
+
if name_func is not None:
|
|
353
|
+
role_name = name_func(name)
|
|
354
354
|
role = self.query.filter(self.name == role_name).first()
|
|
355
355
|
if not role:
|
|
356
356
|
self.create(
|
|
@@ -48,6 +48,7 @@ from mage_ai.data_preparation.models.constants import (
|
|
|
48
48
|
PipelineType,
|
|
49
49
|
)
|
|
50
50
|
from mage_ai.data_preparation.models.pipeline import Pipeline
|
|
51
|
+
from mage_ai.data_preparation.models.project import Project
|
|
51
52
|
from mage_ai.data_preparation.models.triggers import (
|
|
52
53
|
ScheduleInterval,
|
|
53
54
|
ScheduleStatus,
|
|
@@ -934,9 +935,12 @@ class PipelineRun(PipelineRunProjectPlatformMixin, BaseModel):
|
|
|
934
935
|
|
|
935
936
|
@property
|
|
936
937
|
def pipeline_tags(self):
|
|
937
|
-
|
|
938
|
+
try:
|
|
939
|
+
pipeline_config = Pipeline.get_config(self.pipeline_uuid)
|
|
940
|
+
except Exception:
|
|
941
|
+
pipeline_config = dict()
|
|
938
942
|
|
|
939
|
-
return
|
|
943
|
+
return pipeline_config.get('tags') if pipeline_config is not None else []
|
|
940
944
|
|
|
941
945
|
def executable_block_runs(
|
|
942
946
|
self,
|
|
@@ -989,6 +993,9 @@ class PipelineRun(PipelineRunProjectPlatformMixin, BaseModel):
|
|
|
989
993
|
block_runs_all = []
|
|
990
994
|
|
|
991
995
|
data_integration_block_uuids_mapping = {}
|
|
996
|
+
|
|
997
|
+
pipeline_project = Project(pipeline.repo_config)
|
|
998
|
+
|
|
992
999
|
for block_run in self.block_runs:
|
|
993
1000
|
block_runs_all.append(block_run)
|
|
994
1001
|
|
|
@@ -1021,7 +1028,7 @@ class PipelineRun(PipelineRunProjectPlatformMixin, BaseModel):
|
|
|
1021
1028
|
"original": 1,
|
|
1022
1029
|
}
|
|
1023
1030
|
"""
|
|
1024
|
-
if metrics and block and block.is_data_integration():
|
|
1031
|
+
if metrics and block and block.is_data_integration(pipeline_project=pipeline_project):
|
|
1025
1032
|
original_block_uuid = metrics.get('original_block_uuid')
|
|
1026
1033
|
|
|
1027
1034
|
if original_block_uuid and metrics.get('child'):
|
|
@@ -86,6 +86,12 @@ class JobManager:
|
|
|
86
86
|
job_id = self.__job_id(JobType.INTEGRATION_STREAM, id)
|
|
87
87
|
return self.queue.kill_job(job_id)
|
|
88
88
|
|
|
89
|
+
def start(self):
|
|
90
|
+
self.queue.start()
|
|
91
|
+
|
|
92
|
+
def stop(self):
|
|
93
|
+
self.queue.stop()
|
|
94
|
+
|
|
89
95
|
def __job_id(self, job_type: JobType, uid: Union[str, int]):
|
|
90
96
|
return f'{job_type}_{uid}'
|
|
91
97
|
|
|
@@ -108,6 +108,7 @@ class NotificationSender:
|
|
|
108
108
|
pipeline,
|
|
109
109
|
pipeline_run,
|
|
110
110
|
error: str = None,
|
|
111
|
+
stacktrace: str = None,
|
|
111
112
|
summary: str = None,
|
|
112
113
|
) -> None:
|
|
113
114
|
if AlertOn.PIPELINE_RUN_FAILURE in self.config.alert_on:
|
|
@@ -122,6 +123,7 @@ class NotificationSender:
|
|
|
122
123
|
pipeline_run,
|
|
123
124
|
error=error,
|
|
124
125
|
message_template=message_template,
|
|
126
|
+
stacktrace=stacktrace,
|
|
125
127
|
summary=summary,
|
|
126
128
|
)
|
|
127
129
|
|
|
@@ -145,6 +147,7 @@ class NotificationSender:
|
|
|
145
147
|
pipeline,
|
|
146
148
|
pipeline_run,
|
|
147
149
|
error: str = None,
|
|
150
|
+
stacktrace: str = None,
|
|
148
151
|
):
|
|
149
152
|
if text is None or pipeline is None or pipeline_run is None:
|
|
150
153
|
return text
|
|
@@ -155,6 +158,7 @@ class NotificationSender:
|
|
|
155
158
|
pipeline_schedule_id=pipeline_run.pipeline_schedule.id,
|
|
156
159
|
pipeline_schedule_name=pipeline_run.pipeline_schedule.name,
|
|
157
160
|
pipeline_uuid=pipeline.uuid,
|
|
161
|
+
stacktrace=stacktrace,
|
|
158
162
|
)
|
|
159
163
|
|
|
160
164
|
def __send_pipeline_run_message(
|
|
@@ -164,6 +168,7 @@ class NotificationSender:
|
|
|
164
168
|
pipeline_run,
|
|
165
169
|
error: str = None,
|
|
166
170
|
message_template: MessageTemplate = None,
|
|
171
|
+
stacktrace: str = None,
|
|
167
172
|
summary: str = None,
|
|
168
173
|
):
|
|
169
174
|
"""Shared method to send pipeline run message of multiple types (success, failure, etc.).
|
|
@@ -207,18 +212,21 @@ class NotificationSender:
|
|
|
207
212
|
pipeline,
|
|
208
213
|
pipeline_run,
|
|
209
214
|
error=error,
|
|
215
|
+
stacktrace=stacktrace,
|
|
210
216
|
),
|
|
211
217
|
summary=self.__interpolate_vars(
|
|
212
218
|
summary or default_summary,
|
|
213
219
|
pipeline,
|
|
214
220
|
pipeline_run,
|
|
215
221
|
error=error,
|
|
222
|
+
stacktrace=stacktrace,
|
|
216
223
|
),
|
|
217
224
|
details=self.__interpolate_vars(
|
|
218
225
|
details or default_details,
|
|
219
226
|
pipeline,
|
|
220
227
|
pipeline_run,
|
|
221
228
|
error=error,
|
|
229
|
+
stacktrace=stacktrace,
|
|
222
230
|
),
|
|
223
231
|
)
|
|
224
232
|
|
|
@@ -320,11 +320,19 @@ class PipelineScheduler:
|
|
|
320
320
|
@safe_db_query
|
|
321
321
|
def on_pipeline_run_failure(self, error_msg: str) -> None:
|
|
322
322
|
failed_block_runs = self.pipeline_run.failed_block_runs
|
|
323
|
+
stacktrace = None
|
|
323
324
|
for br in failed_block_runs:
|
|
324
325
|
if br.metrics:
|
|
325
326
|
message = br.metrics.get('error', {}).get('message')
|
|
326
327
|
if message:
|
|
327
|
-
|
|
328
|
+
message_split = message.split('\n')
|
|
329
|
+
# Truncate the error message if it has too many lines, set max
|
|
330
|
+
# lines at 50
|
|
331
|
+
if len(message_split) > 50:
|
|
332
|
+
message_split = message_split[-50:]
|
|
333
|
+
message_split.insert(0, '... (error truncated)')
|
|
334
|
+
message = '\n'.join(message_split)
|
|
335
|
+
stacktrace = f'Error for block {br.block_uuid}:\n{message}'
|
|
328
336
|
break
|
|
329
337
|
|
|
330
338
|
asyncio.run(UsageStatisticLogger().pipeline_run_ended(self.pipeline_run))
|
|
@@ -332,6 +340,7 @@ class PipelineScheduler:
|
|
|
332
340
|
pipeline=self.pipeline,
|
|
333
341
|
pipeline_run=self.pipeline_run,
|
|
334
342
|
error=error_msg,
|
|
343
|
+
stacktrace=stacktrace,
|
|
335
344
|
)
|
|
336
345
|
# Cancel block runs that are still in progress for the pipeline run.
|
|
337
346
|
cancel_block_runs_and_jobs(self.pipeline_run, self.pipeline)
|
|
@@ -821,6 +830,8 @@ class PipelineScheduler:
|
|
|
821
830
|
Returns:
|
|
822
831
|
List[BlockRun]: A list of crashed block runs.
|
|
823
832
|
"""
|
|
833
|
+
for b in self.pipeline_run.block_runs:
|
|
834
|
+
b.refresh()
|
|
824
835
|
running_or_queued_block_runs = [b for b in self.pipeline_run.block_runs if b.status in [
|
|
825
836
|
BlockRun.BlockRunStatus.RUNNING,
|
|
826
837
|
BlockRun.BlockRunStatus.QUEUED,
|
|
@@ -1670,7 +1681,11 @@ def gen_pipeline_with_schedules_single_project(
|
|
|
1670
1681
|
# Iterate through pipeline schedules by pipeline to handle pipeline run limits for
|
|
1671
1682
|
# each pipeline.
|
|
1672
1683
|
for pipeline_uuid, active_schedules in pipeline_schedules_by_pipeline.items():
|
|
1673
|
-
|
|
1684
|
+
try:
|
|
1685
|
+
pipeline = Pipeline.get(pipeline_uuid)
|
|
1686
|
+
except Exception as e:
|
|
1687
|
+
print(f'Error fetching pipeline {pipeline_uuid}: {e}')
|
|
1688
|
+
continue
|
|
1674
1689
|
yield pipeline_uuid, pipeline, active_schedules
|
|
1675
1690
|
|
|
1676
1691
|
|
|
@@ -1731,11 +1746,15 @@ def gen_pipeline_with_schedules_project_platform(
|
|
|
1731
1746
|
for pair in pipeline_schedules_by_pipeline_by_repo_path.items():
|
|
1732
1747
|
repo_path, pipeline_schedules_by_pipeline = pair
|
|
1733
1748
|
for pipeline_uuid, active_schedules in pipeline_schedules_by_pipeline.items():
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1749
|
+
try:
|
|
1750
|
+
pipeline = get_pipeline_from_platform(
|
|
1751
|
+
pipeline_uuid,
|
|
1752
|
+
repo_path=repo_path,
|
|
1753
|
+
mapping=pipeline_schedule_repo_paths_to_repo_path_mapping,
|
|
1754
|
+
)
|
|
1755
|
+
except Exception as e:
|
|
1756
|
+
print(f'Error fetching pipeline {pipeline_uuid}: {e}')
|
|
1757
|
+
continue
|
|
1739
1758
|
yield pipeline_uuid, pipeline, active_schedules
|
|
1740
1759
|
|
|
1741
1760
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
1
3
|
from mage_ai.orchestration.queue.config import QueueConfig
|
|
2
4
|
from mage_ai.orchestration.queue.queue import Queue
|
|
3
|
-
from typing import Callable
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class CeleryQueue(Queue):
|
|
@@ -27,3 +28,9 @@ class CeleryQueue(Queue):
|
|
|
27
28
|
|
|
28
29
|
def kill_job(self, job_id: str):
|
|
29
30
|
pass
|
|
31
|
+
|
|
32
|
+
def start(self):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
def stop(self):
|
|
36
|
+
pass
|
|
@@ -36,6 +36,11 @@ class JobStatus(str, Enum):
|
|
|
36
36
|
CANCELLED = 'cancelled'
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
class QueueStatus(str, Enum):
|
|
40
|
+
ACTIVE = 'active'
|
|
41
|
+
INACTIVE = 'inactive'
|
|
42
|
+
|
|
43
|
+
|
|
39
44
|
class ProcessQueue(Queue):
|
|
40
45
|
def __init__(self, queue_config: QueueConfig):
|
|
41
46
|
"""
|
|
@@ -53,6 +58,7 @@ class ProcessQueue(Queue):
|
|
|
53
58
|
jobs.
|
|
54
59
|
|
|
55
60
|
"""
|
|
61
|
+
self.status = QueueStatus.INACTIVE
|
|
56
62
|
self.queue_config = queue_config
|
|
57
63
|
self.process_queue_config = self.queue_config.process_queue_config
|
|
58
64
|
self.queue = mp.Queue()
|
|
@@ -76,12 +82,16 @@ class ProcessQueue(Queue):
|
|
|
76
82
|
|
|
77
83
|
def clean_up_jobs(self):
|
|
78
84
|
"""
|
|
79
|
-
Cleans up completed jobs from the job dictionary.
|
|
85
|
+
1. Cleans up completed jobs from the job dictionary.
|
|
86
|
+
2. Check whether there're jobs need to be killed.
|
|
80
87
|
"""
|
|
81
88
|
job_ids = self.job_dict.keys()
|
|
82
89
|
for job_id in job_ids:
|
|
83
|
-
if job_id in self.job_dict
|
|
84
|
-
|
|
90
|
+
if job_id in self.job_dict:
|
|
91
|
+
if not self.has_job(job_id):
|
|
92
|
+
del self.job_dict[job_id]
|
|
93
|
+
elif self.__should_kill_job(job_id):
|
|
94
|
+
self.kill_job(job_id)
|
|
85
95
|
|
|
86
96
|
def enqueue(self, job_id: str, target: Callable, *args, **kwargs):
|
|
87
97
|
"""
|
|
@@ -94,6 +104,9 @@ class ProcessQueue(Queue):
|
|
|
94
104
|
**kwargs: Keyword arguments for the target function.
|
|
95
105
|
|
|
96
106
|
"""
|
|
107
|
+
if self.status != QueueStatus.ACTIVE:
|
|
108
|
+
self._print('Cannot enqueue a job to an inactive queue.')
|
|
109
|
+
return
|
|
97
110
|
if self.has_job(job_id):
|
|
98
111
|
self._print(f'Job {job_id} exists. Skip enqueue.')
|
|
99
112
|
return
|
|
@@ -155,6 +168,7 @@ class ProcessQueue(Queue):
|
|
|
155
168
|
print(f'Kill job {job_id}, job_dict {self.job_dict}')
|
|
156
169
|
job = self.job_dict.get(job_id)
|
|
157
170
|
if not job:
|
|
171
|
+
self.__set_kill_job(job_id)
|
|
158
172
|
return
|
|
159
173
|
if isinstance(job, int):
|
|
160
174
|
if job == os.getpid():
|
|
@@ -165,6 +179,7 @@ class ProcessQueue(Queue):
|
|
|
165
179
|
except Exception as err:
|
|
166
180
|
print(err)
|
|
167
181
|
self.job_dict[job_id] = JobStatus.CANCELLED
|
|
182
|
+
self.__unset_kill_job(job_id)
|
|
168
183
|
|
|
169
184
|
def start_worker_pool(self):
|
|
170
185
|
"""
|
|
@@ -182,6 +197,28 @@ class ProcessQueue(Queue):
|
|
|
182
197
|
)
|
|
183
198
|
self.worker_pool_proc.start()
|
|
184
199
|
|
|
200
|
+
def start(self):
|
|
201
|
+
self.status = QueueStatus.ACTIVE
|
|
202
|
+
|
|
203
|
+
def stop(self):
|
|
204
|
+
"""
|
|
205
|
+
1. Stop enqueueing new jobs
|
|
206
|
+
2. Clear the queue
|
|
207
|
+
3. Kill all the running jobs
|
|
208
|
+
"""
|
|
209
|
+
self.status = QueueStatus.INACTIVE
|
|
210
|
+
while not self.queue.empty():
|
|
211
|
+
try:
|
|
212
|
+
self.queue.get_nowait()
|
|
213
|
+
except self.queue.Empty:
|
|
214
|
+
break
|
|
215
|
+
job_ids = self.job_dict.keys()
|
|
216
|
+
for job_id in job_ids:
|
|
217
|
+
if job_id in self.job_dict:
|
|
218
|
+
if isinstance(self.job_dict.get(job_id), int):
|
|
219
|
+
self.kill_job(job_id)
|
|
220
|
+
del self.job_dict[job_id]
|
|
221
|
+
|
|
185
222
|
def is_worker_pool_alive(self) -> bool:
|
|
186
223
|
"""
|
|
187
224
|
Checks if the worker pool process is alive.
|
|
@@ -197,6 +234,31 @@ class ProcessQueue(Queue):
|
|
|
197
234
|
def __is_process_alive(self, pid: int) -> bool:
|
|
198
235
|
return psutil.pid_exists(pid)
|
|
199
236
|
|
|
237
|
+
def __redis_key_kill_job(self, job_id):
|
|
238
|
+
return f'kill_job_{job_id}'
|
|
239
|
+
|
|
240
|
+
def __set_kill_job(self, job_id):
|
|
241
|
+
if not self.redis_client:
|
|
242
|
+
return
|
|
243
|
+
return self.redis_client.set(
|
|
244
|
+
self.__redis_key_kill_job(job_id),
|
|
245
|
+
'1',
|
|
246
|
+
ex=LIVENESS_TIMEOUT_SECONDS,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
def __unset_kill_job(self, job_id):
|
|
250
|
+
if not self.redis_client:
|
|
251
|
+
return
|
|
252
|
+
key = self.__redis_key_kill_job(job_id)
|
|
253
|
+
if self.redis_client.get(key):
|
|
254
|
+
self.redis_client.delete(key)
|
|
255
|
+
|
|
256
|
+
def __should_kill_job(self, job_id):
|
|
257
|
+
if not self.redis_client:
|
|
258
|
+
return False
|
|
259
|
+
value = self.redis_client.get(self.__redis_key_kill_job(job_id))
|
|
260
|
+
return value is not None
|
|
261
|
+
|
|
200
262
|
|
|
201
263
|
class Worker(mp.Process):
|
|
202
264
|
def __init__(
|
|
@@ -275,10 +337,11 @@ def poll_job_and_execute(
|
|
|
275
337
|
job_dict: The shared job dictionary.
|
|
276
338
|
|
|
277
339
|
"""
|
|
340
|
+
pid = os.getpid()
|
|
278
341
|
workers = []
|
|
279
342
|
while True:
|
|
280
343
|
workers = [w for w in workers if w.is_alive()]
|
|
281
|
-
print(f'Worker pool size: {len(workers)}')
|
|
344
|
+
print(f'[Process {pid}] Worker pool size: {len(workers)}')
|
|
282
345
|
if not workers and queue.empty():
|
|
283
346
|
break
|
|
284
347
|
while not queue.empty():
|
mage_ai/server/constants.py
CHANGED