mage-ai 0.9.68__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/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/data_preparation/executors/streaming_pipeline_executor.py +77 -7
- mage_ai/data_preparation/models/block/__init__.py +7 -3
- 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/variables.py +2 -2
- mage_ai/data_preparation/models/block/extension/utils.py +1 -0
- mage_ai/data_preparation/models/block/integration/__init__.py +1 -1
- mage_ai/data_preparation/models/block/sql/__init__.py +1 -1
- mage_ai/data_preparation/models/pipeline.py +22 -6
- 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/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/models/oauth.py +4 -4
- mage_ai/orchestration/db/models/schedules.py +9 -2
- mage_ai/orchestration/job_manager.py +6 -0
- mage_ai/orchestration/pipeline_scheduler_original.py +16 -6
- 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/_next/static/{i8pymuJDTVHdWjUP1QSh1 → _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/_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/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/{pipeline-runs-a66b4c7641ae03eb.js → pipeline-runs-3edc6270c5b0e962.js} +1 -1
- 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]/settings-59aca25a5b1d3998.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-cb88fd075a357fcf.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-1bdfda8edc9cf4a8.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-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/_next/static/chunks/pages/settings/workspace/{sync-data-79a4cf66a623e667.js → 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/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.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 +2 -2
- 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_base_path_template/_next/static/{CKCvjsYCf2imD2X8zAOBf → 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_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/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/{pipeline-runs-a66b4c7641ae03eb.js → pipeline-runs-3edc6270c5b0e962.js} +1 -1
- 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]/settings-59aca25a5b1d3998.js +1 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-cb88fd075a357fcf.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-1bdfda8edc9cf4a8.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-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_base_path_template/_next/static/chunks/pages/settings/workspace/{sync-data-79a4cf66a623e667.js → 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_base_path_template/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.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 +2 -2
- 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/settings/keys/auth.py +2 -0
- mage_ai/settings/server.py +1 -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/test_sessions.py +53 -2
- mage_ai/tests/authentication/providers/test_okta.py +43 -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/server/test_server.py +8 -4
- {mage_ai-0.9.68.dist-info → mage_ai-0.9.69.dist-info}/METADATA +4 -4
- {mage_ai-0.9.68.dist-info → mage_ai-0.9.69.dist-info}/RECORD +203 -200
- 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/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/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-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/sign-in-f59d34429fe022ee.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/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/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-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/sign-in-f59d34429fe022ee.js +0 -1
- /mage_ai/server/frontend_dist/_next/static/{i8pymuJDTVHdWjUP1QSh1 → _krrrgup_C-dPOpX36S8I}/_ssgManifest.js +0 -0
- /mage_ai/server/frontend_dist_base_path_template/_next/static/{CKCvjsYCf2imD2X8zAOBf → KLL5mirre9d7_ZeEpaw3s}/_ssgManifest.js +0 -0
- {mage_ai-0.9.68.dist-info → mage_ai-0.9.69.dist-info}/LICENSE +0 -0
- {mage_ai-0.9.68.dist-info → mage_ai-0.9.69.dist-info}/WHEEL +0 -0
- {mage_ai-0.9.68.dist-info → mage_ai-0.9.69.dist-info}/entry_points.txt +0 -0
- {mage_ai-0.9.68.dist-info → mage_ai-0.9.69.dist-info}/top_level.txt +0 -0
|
@@ -62,6 +62,8 @@ from mage_ai.data_preparation.shared.utils import get_template_vars
|
|
|
62
62
|
from mage_ai.data_preparation.templates.utils import copy_template_directory
|
|
63
63
|
from mage_ai.data_preparation.variable_manager import VariableManager
|
|
64
64
|
from mage_ai.orchestration.constants import Entity
|
|
65
|
+
from mage_ai.orchestration.notification.config import NotificationConfig
|
|
66
|
+
from mage_ai.orchestration.notification.sender import NotificationSender
|
|
65
67
|
from mage_ai.settings.platform import build_repo_path_for_all_projects
|
|
66
68
|
from mage_ai.settings.platform.constants import project_platform_activated
|
|
67
69
|
from mage_ai.settings.repo import get_repo_path
|
|
@@ -339,7 +341,7 @@ class Pipeline:
|
|
|
339
341
|
duplicate_pipeline_dict['name'] = duplicate_pipeline_uuid
|
|
340
342
|
safe_write(
|
|
341
343
|
duplicate_pipeline.config_path,
|
|
342
|
-
yaml.dump(duplicate_pipeline_dict)
|
|
344
|
+
yaml.dump(duplicate_pipeline_dict),
|
|
343
345
|
)
|
|
344
346
|
|
|
345
347
|
tags = duplicate_pipeline_dict.get('tags')
|
|
@@ -514,7 +516,7 @@ class Pipeline:
|
|
|
514
516
|
|
|
515
517
|
if not os.path.exists(config_path):
|
|
516
518
|
raise Exception(f'Pipeline {uuid} does not exist.')
|
|
517
|
-
async with aiofiles.open(config_path, mode='r') as f:
|
|
519
|
+
async with aiofiles.open(config_path, mode='r', encoding='utf-8') as f:
|
|
518
520
|
config = yaml.safe_load(await f.read()) or {}
|
|
519
521
|
|
|
520
522
|
if PipelineType.INTEGRATION == config.get('type'):
|
|
@@ -673,6 +675,7 @@ class Pipeline:
|
|
|
673
675
|
analyze_outputs: bool = False,
|
|
674
676
|
build_block_output_stdout: Callable[..., object] = None,
|
|
675
677
|
global_vars=None,
|
|
678
|
+
retry_config=None,
|
|
676
679
|
run_sensors: bool = True,
|
|
677
680
|
run_tests: bool = True,
|
|
678
681
|
update_status: bool = True,
|
|
@@ -689,6 +692,7 @@ class Pipeline:
|
|
|
689
692
|
StreamingPipelineExecutor(self).execute(
|
|
690
693
|
build_block_output_stdout=build_block_output_stdout,
|
|
691
694
|
global_vars=global_vars,
|
|
695
|
+
retry_config=retry_config,
|
|
692
696
|
)
|
|
693
697
|
else:
|
|
694
698
|
root_blocks = []
|
|
@@ -715,7 +719,7 @@ class Pipeline:
|
|
|
715
719
|
def get_config_from_yaml(self):
|
|
716
720
|
if not os.path.exists(self.config_path):
|
|
717
721
|
raise Exception(f'Pipeline {self.uuid} does not exist in repo_path {self.repo_path}.')
|
|
718
|
-
with open(self.config_path) as fp:
|
|
722
|
+
with open(self.config_path, encoding='utf-8') as fp:
|
|
719
723
|
config = yaml.full_load(fp) or {}
|
|
720
724
|
return config
|
|
721
725
|
|
|
@@ -726,6 +730,16 @@ class Pipeline:
|
|
|
726
730
|
config = json.load(f)
|
|
727
731
|
return config
|
|
728
732
|
|
|
733
|
+
def get_notification_sender(self):
|
|
734
|
+
return NotificationSender(
|
|
735
|
+
NotificationConfig.load(
|
|
736
|
+
config=merge_dict(
|
|
737
|
+
self.repo_config.notification_config,
|
|
738
|
+
self.notification_config,
|
|
739
|
+
),
|
|
740
|
+
),
|
|
741
|
+
)
|
|
742
|
+
|
|
729
743
|
def load_config_from_yaml(self):
|
|
730
744
|
catalog = None
|
|
731
745
|
if os.path.exists(self.catalog_config_path):
|
|
@@ -1294,7 +1308,9 @@ class Pipeline:
|
|
|
1294
1308
|
file_path = (configuration.get('file_source') or {}).get('path')
|
|
1295
1309
|
if file_path:
|
|
1296
1310
|
# Check for block name with period to avoid replacing a directory name
|
|
1297
|
-
new_file_path = file_path.replace(
|
|
1311
|
+
new_file_path = file_path.replace(
|
|
1312
|
+
f'{clean_name(block.name)}.', f'{clean_name(name)}.'
|
|
1313
|
+
)
|
|
1298
1314
|
configuration['file_source']['path'] = new_file_path
|
|
1299
1315
|
block_update_payload['configuration'] = configuration
|
|
1300
1316
|
blocks_to_remove_from_cache.append(block.to_dict())
|
|
@@ -2132,7 +2148,7 @@ class Pipeline:
|
|
|
2132
2148
|
'Blocks cannot be added or removed when saving content, please try again.',
|
|
2133
2149
|
)
|
|
2134
2150
|
|
|
2135
|
-
content = yaml.dump(pipeline_dict)
|
|
2151
|
+
content = yaml.dump(pipeline_dict, allow_unicode=True)
|
|
2136
2152
|
|
|
2137
2153
|
safe_write(self.config_path, content)
|
|
2138
2154
|
|
|
@@ -2205,7 +2221,7 @@ class Pipeline:
|
|
|
2205
2221
|
'Blocks cannot be added or removed when saving content, please try again.',
|
|
2206
2222
|
)
|
|
2207
2223
|
|
|
2208
|
-
content = yaml.dump(pipeline_dict)
|
|
2224
|
+
content = yaml.dump(pipeline_dict, allow_unicode=True)
|
|
2209
2225
|
|
|
2210
2226
|
test_path = f'{self.config_path}.test'
|
|
2211
2227
|
async with aiofiles.open(test_path, mode='w', encoding='utf-8') as fp:
|
|
@@ -7,7 +7,7 @@ from typing import Any, Dict, List
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import pandas as pd
|
|
9
9
|
import polars as pl
|
|
10
|
-
from pandas.api.types import is_object_dtype
|
|
10
|
+
from pandas.api.types import infer_dtype, is_object_dtype
|
|
11
11
|
from pandas.core.indexes.range import RangeIndex
|
|
12
12
|
|
|
13
13
|
from mage_ai.data_cleaner.shared.utils import is_geo_dataframe, is_spark_dataframe
|
|
@@ -18,6 +18,7 @@ from mage_ai.data_preparation.models.constants import (
|
|
|
18
18
|
VARIABLE_DIR,
|
|
19
19
|
)
|
|
20
20
|
from mage_ai.data_preparation.models.utils import ( # dask_from_pandas,
|
|
21
|
+
AMBIGUOUS_COLUMN_TYPES,
|
|
21
22
|
STRING_SERIALIZABLE_COLUMN_TYPES,
|
|
22
23
|
apply_transform_pandas,
|
|
23
24
|
cast_column_types,
|
|
@@ -282,7 +283,9 @@ class Variable:
|
|
|
282
283
|
else:
|
|
283
284
|
self.__write_json(data)
|
|
284
285
|
|
|
285
|
-
self.
|
|
286
|
+
if self.variable_type != VariableType.SPARK_DATAFRAME:
|
|
287
|
+
# Not write json file in spark data directory to avoid read error
|
|
288
|
+
self.write_metadata()
|
|
286
289
|
|
|
287
290
|
async def write_data_async(self, data: Any) -> None:
|
|
288
291
|
"""
|
|
@@ -315,7 +318,9 @@ class Variable:
|
|
|
315
318
|
else:
|
|
316
319
|
await self.__write_json_async(data)
|
|
317
320
|
|
|
318
|
-
self.
|
|
321
|
+
if self.variable_type != VariableType.SPARK_DATAFRAME:
|
|
322
|
+
# Not write json file in spark data directory to avoid read error
|
|
323
|
+
self.write_metadata()
|
|
319
324
|
|
|
320
325
|
def write_metadata(self) -> None:
|
|
321
326
|
"""
|
|
@@ -578,10 +583,19 @@ class Variable:
|
|
|
578
583
|
else:
|
|
579
584
|
series_non_null = df_col.dropna()
|
|
580
585
|
if len(series_non_null) > 0:
|
|
581
|
-
|
|
586
|
+
sample_element = series_non_null.iloc[0]
|
|
587
|
+
coltype = type(sample_element)
|
|
588
|
+
coltype_inferred = infer_dtype(series_non_null)
|
|
582
589
|
if is_object_dtype(series_non_null.dtype):
|
|
583
590
|
if coltype.__name__ in STRING_SERIALIZABLE_COLUMN_TYPES:
|
|
584
591
|
cast_coltype = str
|
|
592
|
+
# If the column is a "primitive" type, i.e. int/bool/etc and there is
|
|
593
|
+
# a mix of types in the column, cast to string
|
|
594
|
+
elif (
|
|
595
|
+
not hasattr(sample_element, '__dict__')
|
|
596
|
+
and coltype_inferred in AMBIGUOUS_COLUMN_TYPES
|
|
597
|
+
):
|
|
598
|
+
cast_coltype = str
|
|
585
599
|
else:
|
|
586
600
|
cast_coltype = coltype
|
|
587
601
|
try:
|
|
@@ -310,8 +310,9 @@ def get_cluster_type(repo_path=None) -> Optional[ClusterType]:
|
|
|
310
310
|
|
|
311
311
|
def set_project_uuid_from_metadata() -> None:
|
|
312
312
|
global project_uuid
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
metadata_path = get_metadata_path(root_project=True)
|
|
314
|
+
if os.path.exists(metadata_path):
|
|
315
|
+
with open(metadata_path, 'r', encoding='utf-8') as f:
|
|
315
316
|
config = yml.load(f) or {}
|
|
316
317
|
project_uuid = config.get('project_uuid')
|
|
317
318
|
|
|
@@ -25,7 +25,7 @@ def get_template_vars_no_db(include_python_libraries: Dict = None) -> Dict[str,
|
|
|
25
25
|
try:
|
|
26
26
|
from mage_ai.services.aws.secrets_manager.secrets_manager import get_secret
|
|
27
27
|
kwargs['aws_secret_var'] = get_secret
|
|
28
|
-
except
|
|
28
|
+
except Exception:
|
|
29
29
|
pass
|
|
30
30
|
|
|
31
31
|
try:
|
|
@@ -23,8 +23,8 @@ def export_data_to_mysql(df: DataFrame, **kwargs) -> None:
|
|
|
23
23
|
with MySQL.with_config(ConfigFileLoader(config_path, config_profile)) as loader:
|
|
24
24
|
loader.export(
|
|
25
25
|
df,
|
|
26
|
-
None,
|
|
27
|
-
table_name,
|
|
26
|
+
schema_name=None,
|
|
27
|
+
table_name=table_name,
|
|
28
28
|
index=False, # Specifies whether to include index in exported table
|
|
29
29
|
if_exists='replace', # Specify resolution policy if table name already exists
|
|
30
30
|
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from os import path
|
|
2
|
+
|
|
3
|
+
from pandas import DataFrame
|
|
4
|
+
|
|
5
|
+
from mage_ai.settings.repo import get_repo_path
|
|
6
|
+
from mage_ai.io.config import ConfigFileLoader
|
|
7
|
+
from mage_ai.io.oracledb import OracleDB
|
|
8
|
+
|
|
9
|
+
if 'data_exporter' not in globals():
|
|
10
|
+
from mage_ai.data_preparation.decorators import data_exporter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@data_exporter
|
|
14
|
+
def export_data_to_oracledb(df: DataFrame, **kwargs) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Template to export data to Oracledb.
|
|
17
|
+
"""
|
|
18
|
+
config_path = path.join(get_repo_path(), 'io_config.yaml')
|
|
19
|
+
config_profile = 'default'
|
|
20
|
+
table_name = 'your_table_name'
|
|
21
|
+
|
|
22
|
+
with OracleDB.with_config(ConfigFileLoader(config_path, config_profile)) as exporter:
|
|
23
|
+
exporter.export(
|
|
24
|
+
df,
|
|
25
|
+
table_name=table_name,
|
|
26
|
+
if_exists='replace', # Specify resolution policy if table name already exists
|
|
27
|
+
)
|
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:
|