mage-ai 0.9.75__py3-none-any.whl → 0.9.76__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/PipelineSchedulePolicy.py +1 -0
- mage_ai/api/presenters/PipelineSchedulePresenter.py +11 -2
- mage_ai/api/resources/PipelineScheduleResource.py +11 -3
- mage_ai/api/resources/PipelineTriggerResource.py +3 -1
- mage_ai/api/resources/SessionResource.py +2 -2
- mage_ai/api/resources/SyncResource.py +1 -1
- mage_ai/api/resources/UserResource.py +1 -1
- mage_ai/data_integrations/destinations/constants.py +2 -0
- mage_ai/data_integrations/sources/constants.py +2 -0
- mage_ai/data_preparation/models/block/__init__.py +0 -1
- mage_ai/data_preparation/models/block/dbt/dbt_adapter.py +20 -8
- mage_ai/data_preparation/models/block/dynamic/constants.py +0 -1
- mage_ai/data_preparation/models/block/dynamic/counter.py +1 -3
- mage_ai/data_preparation/models/block/r/__init__.py +16 -5
- mage_ai/data_preparation/models/block/sql/__init__.py +2 -0
- mage_ai/data_preparation/models/block/sql/mssql.py +8 -0
- mage_ai/data_preparation/models/block/sql/utils/shared.py +6 -2
- mage_ai/data_preparation/models/constants.py +1 -0
- mage_ai/data_preparation/models/pipeline.py +1 -1
- mage_ai/data_preparation/templates/constants.py +7 -0
- mage_ai/data_preparation/templates/data_loaders/airtable.py +28 -0
- mage_ai/data_preparation/templates/repo/io_config.yaml +2 -0
- mage_ai/io/airtable.py +104 -0
- mage_ai/io/base.py +1 -0
- mage_ai/io/bigquery.py +36 -0
- mage_ai/io/config.py +5 -0
- mage_ai/io/mssql.py +5 -0
- mage_ai/io/mysql.py +6 -1
- mage_ai/io/redshift.py +13 -0
- mage_ai/io/sql.py +1 -0
- mage_ai/orchestration/db/__init__.py +20 -0
- mage_ai/orchestration/db/models/schedules.py +2 -5
- mage_ai/orchestration/db/models/secrets.py +11 -1
- mage_ai/orchestration/metrics/pipeline_run.py +1 -1
- mage_ai/orchestration/pipeline_scheduler_original.py +4 -5
- mage_ai/orchestration/pipeline_scheduler_project_platform.py +4 -5
- mage_ai/orchestration/utils/distributed_lock.py +8 -1
- mage_ai/server/api/triggers.py +9 -0
- mage_ai/server/constants.py +1 -1
- mage_ai/server/frontend_dist/404.html +2 -2
- mage_ai/server/frontend_dist/_next/static/chunks/449-5e2253c6aba42557.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-782dd4a6b13e1c42.js +2 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users-5db54821a3059c69.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/overview-f65416f6dbe30ad3.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-03d9bca3bc5e6088.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-d25d07db166cbb04.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/users-fa61dc6c1370e6a5.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/{webpack-0bc44da590c7cf85.js → webpack-b9a067f3bd0a3a05.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/{38-PtskJFUTYUpRhT1qF_ → qR0jauUABqPaFMjUsYeoG}/_buildManifest.js +1 -1
- 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/overview.html +2 -2
- mage_ai/server/frontend_dist/manage/pipeline-runs.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/v2/canvas.html +2 -2
- mage_ai/server/frontend_dist/v2.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/chunks/449-5e2253c6aba42557.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-ee5e328aaf51c698.js +2 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users-5db54821a3059c69.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/overview-f65416f6dbe30ad3.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-03d9bca3bc5e6088.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-d25d07db166cbb04.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/users-fa61dc6c1370e6a5.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{webpack-12ad70eb5c31aa92.js → webpack-5f4be622608d9267.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/{dxnSzgIvSG4Ke5LM-tPQX → iCySon3_GCldnbC5U7C-s}/_buildManifest.js +1 -1
- 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/overview.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/pipeline-runs.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/v2/canvas.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/v2.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/version-control.html +2 -2
- mage_ai/settings/server.py +2 -0
- mage_ai/tests/data_preparation/models/block/dynamic/test_counter.py +1 -3
- mage_ai/usage_statistics/logger.py +99 -15
- {mage_ai-0.9.75.dist-info → mage_ai-0.9.76.dist-info}/METADATA +119 -103
- {mage_ai-0.9.75.dist-info → mage_ai-0.9.76.dist-info}/RECORD +188 -186
- {mage_ai-0.9.75.dist-info → mage_ai-0.9.76.dist-info}/WHEEL +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/449-f689774546860ca4.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-13bf3b7dcef50c29.js +0 -2
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users-b99379d0aa6a8c25.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/overview-e51cd04bd4d1fffe.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-0abf8a1b7243f93b.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-38187954b6ec4b40.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/users-3ee783f5139c76a1.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/449-f689774546860ca4.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-0392ef723ea2c6f8.js +0 -2
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users-b99379d0aa6a8c25.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/overview-e51cd04bd4d1fffe.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-0abf8a1b7243f93b.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-38187954b6ec4b40.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/users-3ee783f5139c76a1.js +0 -1
- /mage_ai/server/frontend_dist/_next/static/chunks/pages/{_app-13bf3b7dcef50c29.js.LICENSE.txt → _app-782dd4a6b13e1c42.js.LICENSE.txt} +0 -0
- /mage_ai/server/frontend_dist/_next/static/{38-PtskJFUTYUpRhT1qF_ → qR0jauUABqPaFMjUsYeoG}/_ssgManifest.js +0 -0
- /mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{_app-0392ef723ea2c6f8.js.LICENSE.txt → _app-ee5e328aaf51c698.js.LICENSE.txt} +0 -0
- /mage_ai/server/frontend_dist_base_path_template/_next/static/{dxnSzgIvSG4Ke5LM-tPQX → iCySon3_GCldnbC5U7C-s}/_ssgManifest.js +0 -0
- {mage_ai-0.9.75.dist-info → mage_ai-0.9.76.dist-info}/entry_points.txt +0 -0
- {mage_ai-0.9.75.dist-info → mage_ai-0.9.76.dist-info/licenses}/LICENSE +0 -0
- {mage_ai-0.9.75.dist-info → mage_ai-0.9.76.dist-info}/top_level.txt +0 -0
|
@@ -100,6 +100,7 @@ PipelineSchedulePolicy.allow_read(PipelineSchedulePresenter.default_attributes +
|
|
|
100
100
|
PipelineSchedulePolicy.allow_read(PipelineSchedulePresenter.default_attributes + [
|
|
101
101
|
'event_matchers',
|
|
102
102
|
'next_pipeline_run_date',
|
|
103
|
+
'rotate_token',
|
|
103
104
|
'tags',
|
|
104
105
|
], scopes=[
|
|
105
106
|
OauthScope.CLIENT_PRIVATE,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from mage_ai.api.operations import constants
|
|
2
2
|
from mage_ai.api.presenters.BasePresenter import BasePresenter
|
|
3
|
+
from mage_ai.settings.server import HIDE_API_TRIGGER_TOKEN
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class PipelineSchedulePresenter(BasePresenter):
|
|
@@ -32,8 +33,6 @@ class PipelineSchedulePresenter(BasePresenter):
|
|
|
32
33
|
])
|
|
33
34
|
data['tags'] = sorted([tag.name for tag in self.get_tag_associations])
|
|
34
35
|
data['next_pipeline_run_date'] = self.model.next_execution_date()
|
|
35
|
-
|
|
36
|
-
return data
|
|
37
36
|
elif 'with_runtime_average' == display_format:
|
|
38
37
|
data = self.model.to_dict()
|
|
39
38
|
data['runtime_average'] = self.model.runtime_average()
|
|
@@ -47,6 +46,16 @@ class PipelineSchedulePresenter(BasePresenter):
|
|
|
47
46
|
else:
|
|
48
47
|
data = self.model.to_dict()
|
|
49
48
|
|
|
49
|
+
if display_format == constants.UPDATE:
|
|
50
|
+
rotate_token = kwargs.get(
|
|
51
|
+
'payload', dict(),
|
|
52
|
+
).get(
|
|
53
|
+
'pipeline_schedule', dict(),
|
|
54
|
+
).get('rotate_token')
|
|
55
|
+
else:
|
|
56
|
+
rotate_token = False
|
|
57
|
+
if HIDE_API_TRIGGER_TOKEN and not rotate_token:
|
|
58
|
+
data['token'] = '[API_TOKEN_PLACEHOLDER]'
|
|
50
59
|
return data
|
|
51
60
|
|
|
52
61
|
|
|
@@ -29,7 +29,8 @@ from mage_ai.orchestration.db.models.tags import (
|
|
|
29
29
|
)
|
|
30
30
|
from mage_ai.settings.platform import project_platform_activated
|
|
31
31
|
from mage_ai.settings.repo import get_repo_path
|
|
32
|
-
from mage_ai.
|
|
32
|
+
from mage_ai.settings.server import HIDE_API_TRIGGER_TOKEN
|
|
33
|
+
from mage_ai.shared.hash import ignore_keys, merge_dict
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
class PipelineScheduleResource(DatabaseResource):
|
|
@@ -282,6 +283,7 @@ class PipelineScheduleResource(DatabaseResource):
|
|
|
282
283
|
|
|
283
284
|
@safe_db_query
|
|
284
285
|
def update(self, payload, **kwargs):
|
|
286
|
+
# Update associated event matchers
|
|
285
287
|
arr = payload.pop('event_matchers', None)
|
|
286
288
|
event_matchers = []
|
|
287
289
|
if arr is not None:
|
|
@@ -320,6 +322,7 @@ class PipelineScheduleResource(DatabaseResource):
|
|
|
320
322
|
]
|
|
321
323
|
em.update(pipeline_schedules=ps)
|
|
322
324
|
|
|
325
|
+
# Update associated tags
|
|
323
326
|
tag_names = payload.pop('tags', None)
|
|
324
327
|
if tag_names is not None:
|
|
325
328
|
# 1. Fetch all tag associations
|
|
@@ -408,7 +411,11 @@ class PipelineScheduleResource(DatabaseResource):
|
|
|
408
411
|
|
|
409
412
|
old_name = self.model.name
|
|
410
413
|
|
|
411
|
-
|
|
414
|
+
# Rotate token
|
|
415
|
+
if payload.get('rotate_token'):
|
|
416
|
+
payload['token'] = uuid.uuid4().hex
|
|
417
|
+
|
|
418
|
+
resource = super().update(ignore_keys(payload, ['rotate_token']))
|
|
412
419
|
updated_model = resource.model
|
|
413
420
|
|
|
414
421
|
repo_path = get_repo_path(user=self.current_user)
|
|
@@ -424,9 +431,10 @@ class PipelineScheduleResource(DatabaseResource):
|
|
|
424
431
|
sla=updated_model.sla,
|
|
425
432
|
start_time=updated_model.start_time,
|
|
426
433
|
status=updated_model.status,
|
|
427
|
-
token=updated_model.token,
|
|
428
434
|
variables=updated_model.variables,
|
|
429
435
|
)
|
|
436
|
+
if not HIDE_API_TRIGGER_TOKEN:
|
|
437
|
+
trigger.token = updated_model.token
|
|
430
438
|
|
|
431
439
|
update_only_if_exists = (
|
|
432
440
|
not pipeline.should_save_trigger_in_code_automatically()
|
|
@@ -8,6 +8,7 @@ from mage_ai.data_preparation.models.triggers import (
|
|
|
8
8
|
from mage_ai.orchestration.db import safe_db_query
|
|
9
9
|
from mage_ai.orchestration.db.models.schedules import PipelineSchedule
|
|
10
10
|
from mage_ai.settings.repo import get_repo_path
|
|
11
|
+
from mage_ai.settings.server import HIDE_API_TRIGGER_TOKEN
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class PipelineTriggerResource(GenericResource):
|
|
@@ -50,9 +51,10 @@ class PipelineTriggerResource(GenericResource):
|
|
|
50
51
|
sla=pipeline_schedule.sla,
|
|
51
52
|
start_time=pipeline_schedule.start_time,
|
|
52
53
|
status=pipeline_schedule.status,
|
|
53
|
-
token=pipeline_schedule.token,
|
|
54
54
|
variables=pipeline_schedule.variables,
|
|
55
55
|
)
|
|
56
|
+
if not HIDE_API_TRIGGER_TOKEN:
|
|
57
|
+
trigger.token = pipeline_schedule.token
|
|
56
58
|
else:
|
|
57
59
|
trigger = Trigger(**payload)
|
|
58
60
|
|
|
@@ -6,7 +6,7 @@ from mage_ai.authentication.ldap import new_ldap_connection
|
|
|
6
6
|
from mage_ai.authentication.oauth2 import encode_token, generate_access_token
|
|
7
7
|
from mage_ai.authentication.passwords import verify_password
|
|
8
8
|
from mage_ai.authentication.providers.constants import NAME_TO_PROVIDER
|
|
9
|
-
from mage_ai.orchestration.db import safe_db_query
|
|
9
|
+
from mage_ai.orchestration.db import safe_db_query, safe_db_query_async
|
|
10
10
|
from mage_ai.orchestration.db.models.oauth import Role, User
|
|
11
11
|
from mage_ai.settings import (
|
|
12
12
|
AUTHENTICATION_MODE,
|
|
@@ -20,7 +20,7 @@ from mage_ai.usage_statistics.logger import UsageStatisticLogger
|
|
|
20
20
|
|
|
21
21
|
class SessionResource(BaseResource):
|
|
22
22
|
@classmethod
|
|
23
|
-
@
|
|
23
|
+
@safe_db_query_async
|
|
24
24
|
async def create(self, payload, _, **kwargs):
|
|
25
25
|
email = payload.get('email')
|
|
26
26
|
password = payload.get('password')
|
|
@@ -163,7 +163,7 @@ class SyncResource(GenericResource):
|
|
|
163
163
|
access_token = user_payload.pop('access_token', None)
|
|
164
164
|
if access_token:
|
|
165
165
|
secret_name = get_access_token_secret_name(user=user)
|
|
166
|
-
secret = Secret.
|
|
166
|
+
secret = Secret.repo_query.filter(Secret.name == secret_name).one_or_none()
|
|
167
167
|
if secret:
|
|
168
168
|
secret.delete()
|
|
169
169
|
create_secret(secret_name, access_token, repo_name=repo_name)
|
|
@@ -5,6 +5,7 @@ DESTINATIONS = [
|
|
|
5
5
|
dict(name='Clickhouse'),
|
|
6
6
|
dict(name='Delta Lake Azure'),
|
|
7
7
|
dict(name='Delta Lake S3'),
|
|
8
|
+
dict(name='Doris'),
|
|
8
9
|
dict(name='Elasticsearch'),
|
|
9
10
|
dict(name='Google Cloud Storage'),
|
|
10
11
|
dict(name='Kafka'),
|
|
@@ -21,5 +22,6 @@ DESTINATIONS = [
|
|
|
21
22
|
dict(name='Redshift'),
|
|
22
23
|
dict(name='Salesforce'),
|
|
23
24
|
dict(name='Snowflake'),
|
|
25
|
+
dict(name='Teradata'),
|
|
24
26
|
dict(name='Trino'),
|
|
25
27
|
]
|
|
@@ -3,6 +3,7 @@ from mage_ai.shared.hash import index_by
|
|
|
3
3
|
|
|
4
4
|
SQL_SOURCES = [
|
|
5
5
|
dict(name='BigQuery'),
|
|
6
|
+
dict(name='Doris'),
|
|
6
7
|
dict(
|
|
7
8
|
name='Microsoft SQL Server',
|
|
8
9
|
uuid='mssql',
|
|
@@ -53,6 +54,7 @@ SOURCES = sorted([
|
|
|
53
54
|
dict(name='Sftp'),
|
|
54
55
|
dict(name='Stripe'),
|
|
55
56
|
dict(name='Tableau'),
|
|
57
|
+
dict(name='Teradata'),
|
|
56
58
|
dict(name='Twitter Ads'),
|
|
57
59
|
dict(name='Zendesk'),
|
|
58
60
|
] + SQL_SOURCES, key=lambda x: x['name'])
|
|
@@ -5,7 +5,9 @@ from typing import Any, Dict, Optional, Tuple, Union
|
|
|
5
5
|
|
|
6
6
|
import dbt.flags as flags
|
|
7
7
|
import pandas as pd
|
|
8
|
-
from dbt.adapters.base import BaseRelation
|
|
8
|
+
from dbt.adapters.base.relation import BaseRelation
|
|
9
|
+
from dbt.adapters.contracts.connection import AdapterResponse, Credentials
|
|
10
|
+
from dbt.adapters.contracts.relation import RelationType
|
|
9
11
|
from dbt.adapters.factory import (
|
|
10
12
|
Adapter,
|
|
11
13
|
cleanup_connections,
|
|
@@ -13,10 +15,10 @@ from dbt.adapters.factory import (
|
|
|
13
15
|
register_adapter,
|
|
14
16
|
reset_adapters,
|
|
15
17
|
)
|
|
16
|
-
from dbt.config.
|
|
18
|
+
from dbt.config.project import read_project_flags
|
|
17
19
|
from dbt.config.runtime import RuntimeConfig
|
|
18
|
-
from dbt.
|
|
19
|
-
from dbt.
|
|
20
|
+
from dbt.context.providers import generate_runtime_macro_context
|
|
21
|
+
from dbt.mp_context import get_mp_context
|
|
20
22
|
|
|
21
23
|
from mage_ai.data_preparation.models.block.dbt.profiles import Profiles
|
|
22
24
|
from mage_ai.shared.environments import is_debug
|
|
@@ -79,7 +81,11 @@ class DBTAdapter:
|
|
|
79
81
|
# remove interpolated profiles.yml
|
|
80
82
|
self.__profiles.clean()
|
|
81
83
|
|
|
82
|
-
def execute(
|
|
84
|
+
def execute(
|
|
85
|
+
self,
|
|
86
|
+
sql: str,
|
|
87
|
+
fetch: bool = False
|
|
88
|
+
) -> Tuple[AdapterResponse, pd.DataFrame]:
|
|
83
89
|
"""
|
|
84
90
|
Executes any sql statement using the dbt adapter.
|
|
85
91
|
|
|
@@ -135,6 +141,8 @@ class DBTAdapter:
|
|
|
135
141
|
package
|
|
136
142
|
)
|
|
137
143
|
|
|
144
|
+
self.__adapter.set_macro_resolver(manifest)
|
|
145
|
+
|
|
138
146
|
# create a context for the macro (e.g. downstream macros)
|
|
139
147
|
from dbt.context.providers import generate_runtime_macro_context
|
|
140
148
|
macro_context = generate_runtime_macro_context(
|
|
@@ -203,7 +211,7 @@ class DBTAdapter:
|
|
|
203
211
|
# set dbt flags
|
|
204
212
|
# Need to add profiles.yml file
|
|
205
213
|
try:
|
|
206
|
-
user_config =
|
|
214
|
+
user_config = read_project_flags(self.project_path, profiles_path)
|
|
207
215
|
except Exception as err:
|
|
208
216
|
print(f'[ERROR] DBTAdapter.open: {err}.')
|
|
209
217
|
|
|
@@ -211,7 +219,10 @@ class DBTAdapter:
|
|
|
211
219
|
not profiles_path.endswith('profiles.yml'):
|
|
212
220
|
|
|
213
221
|
try:
|
|
214
|
-
user_config =
|
|
222
|
+
user_config = read_project_flags(
|
|
223
|
+
self.project_path,
|
|
224
|
+
os.path.join(profiles_path, 'profiles.yml')
|
|
225
|
+
)
|
|
215
226
|
except Exception as err2:
|
|
216
227
|
print(f'[ERROR] DBTAdapter.open: {err2}.')
|
|
217
228
|
raise err
|
|
@@ -227,9 +238,10 @@ class DBTAdapter:
|
|
|
227
238
|
config = RuntimeConfig.from_args(adapter_config)
|
|
228
239
|
reset_adapters()
|
|
229
240
|
# register the correct adapter from config
|
|
230
|
-
register_adapter(config)
|
|
241
|
+
register_adapter(config, mp_context=get_mp_context())
|
|
231
242
|
# load the adapter
|
|
232
243
|
self.__adapter = get_adapter(config)
|
|
244
|
+
self.__adapter.set_macro_context_generator(generate_runtime_macro_context)
|
|
233
245
|
# connect
|
|
234
246
|
self.__adapter.acquire_connection('mage_dbt_adapter_' + uuid.uuid4().hex)
|
|
235
247
|
return self
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
CHILD_DATA_VARIABLE_UUID = 'output_0'
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
from typing import Any, Iterable, List, Optional, Sequence
|
|
2
2
|
|
|
3
3
|
from mage_ai.data.tabular.reader import read_metadata
|
|
4
|
-
from mage_ai.data_preparation.models.block.dynamic.constants import (
|
|
5
|
-
CHILD_DATA_VARIABLE_UUID,
|
|
6
|
-
)
|
|
7
4
|
from mage_ai.data_preparation.models.block.dynamic.utils import (
|
|
8
5
|
is_dynamic_block,
|
|
9
6
|
is_dynamic_block_child,
|
|
10
7
|
should_reduce_output,
|
|
11
8
|
)
|
|
9
|
+
from mage_ai.data_preparation.models.constants import CHILD_DATA_VARIABLE_UUID
|
|
12
10
|
from mage_ai.data_preparation.models.variables.cache import VariableAggregateCache
|
|
13
11
|
from mage_ai.data_preparation.models.variables.constants import (
|
|
14
12
|
VariableAggregateDataType,
|
|
@@ -8,7 +8,10 @@ import pandas as pd
|
|
|
8
8
|
import simplejson
|
|
9
9
|
|
|
10
10
|
from mage_ai.data_preparation.models.block import Block
|
|
11
|
-
from mage_ai.data_preparation.models.constants import
|
|
11
|
+
from mage_ai.data_preparation.models.constants import (
|
|
12
|
+
CHILD_DATA_VARIABLE_UUID,
|
|
13
|
+
BlockType,
|
|
14
|
+
)
|
|
12
15
|
from mage_ai.data_preparation.models.variables.constants import (
|
|
13
16
|
DATAFRAME_CSV_FILE,
|
|
14
17
|
VariableType,
|
|
@@ -81,7 +84,10 @@ def execute_r_code(
|
|
|
81
84
|
|
|
82
85
|
if len(output_variable_objects) > 0:
|
|
83
86
|
df = pd.read_csv(
|
|
84
|
-
os.path.join(
|
|
87
|
+
os.path.join(
|
|
88
|
+
output_variable_objects[0].variable_path,
|
|
89
|
+
DATAFRAME_CSV_FILE
|
|
90
|
+
)
|
|
85
91
|
)
|
|
86
92
|
else:
|
|
87
93
|
df = None
|
|
@@ -129,14 +135,19 @@ def __render_r_script(
|
|
|
129
135
|
raise Exception(
|
|
130
136
|
f"Block execution for {block.type} with R language is not supported.",
|
|
131
137
|
)
|
|
132
|
-
template = template_env.get_template(
|
|
138
|
+
template = template_env.get_template(
|
|
139
|
+
BLOCK_TYPE_TO_EXECUTION_TEMPLATE[block.type]
|
|
140
|
+
)
|
|
133
141
|
|
|
134
142
|
output_variable_object = block.variable_object(
|
|
135
|
-
|
|
143
|
+
CHILD_DATA_VARIABLE_UUID,
|
|
136
144
|
execution_partition=execution_partition,
|
|
137
145
|
)
|
|
138
146
|
os.makedirs(output_variable_object.variable_path, exist_ok=True)
|
|
139
|
-
output_path = os.path.join(
|
|
147
|
+
output_path = os.path.join(
|
|
148
|
+
output_variable_object.variable_path,
|
|
149
|
+
DATAFRAME_CSV_FILE
|
|
150
|
+
)
|
|
140
151
|
|
|
141
152
|
global_vars_str = __render_global_vars(global_vars=global_vars)
|
|
142
153
|
|
|
@@ -150,6 +150,7 @@ def execute_sql_code(
|
|
|
150
150
|
interpolate_vars_options = dict(
|
|
151
151
|
block=block,
|
|
152
152
|
dynamic_block_index=dynamic_block_index,
|
|
153
|
+
execution_partition=execution_partition,
|
|
153
154
|
global_vars=global_vars,
|
|
154
155
|
)
|
|
155
156
|
|
|
@@ -368,6 +369,7 @@ def execute_sql_code(
|
|
|
368
369
|
loader,
|
|
369
370
|
block,
|
|
370
371
|
query_string,
|
|
372
|
+
disable_query_preprocessing=disable_query_preprocessing,
|
|
371
373
|
configuration=configuration,
|
|
372
374
|
should_query=should_query,
|
|
373
375
|
)
|
|
@@ -67,9 +67,17 @@ def execute_raw_sql(
|
|
|
67
67
|
query_string: str,
|
|
68
68
|
configuration: Dict = None,
|
|
69
69
|
should_query: bool = False,
|
|
70
|
+
disable_query_preprocessing: bool = False,
|
|
70
71
|
) -> List[Any]:
|
|
71
72
|
if configuration is None:
|
|
72
73
|
configuration = {}
|
|
74
|
+
|
|
75
|
+
if disable_query_preprocessing:
|
|
76
|
+
return loader.execute_query_raw(
|
|
77
|
+
query_string,
|
|
78
|
+
configuration=configuration,
|
|
79
|
+
)
|
|
80
|
+
|
|
73
81
|
queries = []
|
|
74
82
|
fetch_query_at_indexes = []
|
|
75
83
|
|
|
@@ -232,7 +232,8 @@ def interpolate_vars(
|
|
|
232
232
|
content: str,
|
|
233
233
|
global_vars: Dict = None,
|
|
234
234
|
block=None,
|
|
235
|
-
dynamic_block_index: int = None
|
|
235
|
+
dynamic_block_index: int = None,
|
|
236
|
+
execution_partition: str = None,
|
|
236
237
|
) -> str :
|
|
237
238
|
if not content:
|
|
238
239
|
return content
|
|
@@ -241,7 +242,10 @@ def interpolate_vars(
|
|
|
241
242
|
|
|
242
243
|
if block:
|
|
243
244
|
content = block.interpolate_content(
|
|
244
|
-
content,
|
|
245
|
+
content,
|
|
246
|
+
variables=global_vars,
|
|
247
|
+
dynamic_block_index=dynamic_block_index,
|
|
248
|
+
execution_partition=execution_partition,
|
|
245
249
|
)
|
|
246
250
|
|
|
247
251
|
return Template(
|
|
@@ -2331,7 +2331,7 @@ class Pipeline:
|
|
|
2331
2331
|
):
|
|
2332
2332
|
# Introduce a small delay to prevent multiple changes from generating
|
|
2333
2333
|
# identical timestamps for the pipeline YAML file
|
|
2334
|
-
time.sleep(0.
|
|
2334
|
+
time.sleep(0.0005)
|
|
2335
2335
|
|
|
2336
2336
|
blocks_current = sorted([b.uuid for b in self.blocks_by_uuid.values()])
|
|
2337
2337
|
|
|
@@ -19,6 +19,13 @@ GROUP_ROW_ACTIONS = 'Row actions'
|
|
|
19
19
|
GROUP_SHIFT = 'Shift'
|
|
20
20
|
|
|
21
21
|
TEMPLATES = [
|
|
22
|
+
dict(
|
|
23
|
+
block_type=BlockType.DATA_LOADER,
|
|
24
|
+
description='Load a Table from Airtable App.',
|
|
25
|
+
language=BlockLanguage.PYTHON,
|
|
26
|
+
name='Airtable',
|
|
27
|
+
path='data_loaders/airtable.py',
|
|
28
|
+
),
|
|
22
29
|
dict(
|
|
23
30
|
block_type=BlockType.DATA_LOADER,
|
|
24
31
|
description='Load a Delta Table from Amazon S3.',
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{% extends "data_loaders/default.jinja" %}
|
|
2
|
+
{% block imports %}
|
|
3
|
+
from mage_ai.settings.repo import get_repo_path
|
|
4
|
+
from mage_ai.io.config import ConfigFileLoader
|
|
5
|
+
from mage_ai.io.airtable import Airtable
|
|
6
|
+
from os import path
|
|
7
|
+
{{ super() -}}
|
|
8
|
+
{% endblock %}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
{% block content %}
|
|
12
|
+
@data_loader
|
|
13
|
+
def load_data_from_airtable(*args, **kwargs):
|
|
14
|
+
"""
|
|
15
|
+
Template for loading data from Airtable.
|
|
16
|
+
Specify your configuration settings in 'io_config.yaml'.
|
|
17
|
+
"""
|
|
18
|
+
config_path = path.join(get_repo_path(), 'io_config.yaml')
|
|
19
|
+
config_profile = 'default'
|
|
20
|
+
|
|
21
|
+
base_id = 'your_base_id'
|
|
22
|
+
table_name = 'your_table_name'
|
|
23
|
+
|
|
24
|
+
return Airtable.with_config(ConfigFileLoader(config_path, config_profile)).load(
|
|
25
|
+
base_id=base_id,
|
|
26
|
+
table_name=table_name
|
|
27
|
+
)
|
|
28
|
+
{% endblock %}
|
|
@@ -11,6 +11,8 @@ default:
|
|
|
11
11
|
ALGOLIA_APP_ID: app_id
|
|
12
12
|
ALGOLIA_API_KEY: api_key
|
|
13
13
|
ALGOLIA_INDEX_NAME: index_name
|
|
14
|
+
# Airtable
|
|
15
|
+
AIRTABLE_ACCESS_TOKEN: token
|
|
14
16
|
# Azure
|
|
15
17
|
AZURE_CLIENT_ID: "{{ env_var('AZURE_CLIENT_ID') }}"
|
|
16
18
|
AZURE_CLIENT_SECRET: "{{ env_var('AZURE_CLIENT_SECRET') }}"
|
mage_ai/io/airtable.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import polars as pl
|
|
5
|
+
from pyairtable.api import Api
|
|
6
|
+
|
|
7
|
+
from mage_ai.io.base import BaseIO
|
|
8
|
+
from mage_ai.io.config import BaseConfigLoader, ConfigKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Airtable(BaseIO):
|
|
12
|
+
"""
|
|
13
|
+
Handles data transfer between Airtable tables and the Mage app.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
token: str,
|
|
19
|
+
verbose: bool = True,
|
|
20
|
+
**kwargs) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Initializes a connection to Airtable.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
token (str): Airtable API token.
|
|
26
|
+
verbose (bool, optional): Whether to print verbose output. Defaults to True.
|
|
27
|
+
**kwargs: Additional keyword arguments to pass to the pyairtable Api constructor.
|
|
28
|
+
"""
|
|
29
|
+
super().__init__(verbose=verbose)
|
|
30
|
+
self.client = Api(token, **kwargs) # Create the Airtable API client
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def with_config(
|
|
34
|
+
cls,
|
|
35
|
+
config: BaseConfigLoader
|
|
36
|
+
) -> 'Airtable':
|
|
37
|
+
"""
|
|
38
|
+
Initializes an Airtable client from a configuration loader.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
config (BaseConfigLoader): Configuration loader object.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ValueError: If no valid Airtable API token is found in the configuration.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Airtable: An instance of the Airtable class.
|
|
48
|
+
"""
|
|
49
|
+
if ConfigKey.AIRTABLE_ACCESS_TOKEN not in config:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
'No valid API token found for Airtable.'
|
|
52
|
+
'You must specify your access token in your config.'
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return cls(
|
|
56
|
+
token=config[ConfigKey.AIRTABLE_ACCESS_TOKEN]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def load(
|
|
60
|
+
self,
|
|
61
|
+
base_id: str,
|
|
62
|
+
table_name: str,
|
|
63
|
+
**kwargs,
|
|
64
|
+
) -> pd.DataFrame:
|
|
65
|
+
"""
|
|
66
|
+
Loads data from an Airtable table into a Pandas DataFrame.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
base_id (str): The ID of the Airtable base (e.g., 'app*****').
|
|
70
|
+
table_name (str): The name or ID of the Airtable table (e.g., 'tbl*****').
|
|
71
|
+
**kwargs: Additional keyword arguments to pass to the pyairtable table.all() method.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
DataFrame: A Pandas DataFrame containing the data from the Airtable table.
|
|
75
|
+
"""
|
|
76
|
+
with self.printer.print_msg(
|
|
77
|
+
f'Loading data frame from table \'{table_name}\' at airtable app \'{base_id}\''
|
|
78
|
+
):
|
|
79
|
+
table = self.client.table(base_id, table_name) # Get the Airtable table
|
|
80
|
+
data = table.all(**kwargs) # Fetch all records from the table
|
|
81
|
+
|
|
82
|
+
# Flatten the Airtable data structure into a list of dictionaries
|
|
83
|
+
flattened_data = []
|
|
84
|
+
for record in data:
|
|
85
|
+
flattened_record = {
|
|
86
|
+
'id': record['id'],
|
|
87
|
+
'createdTime': record['createdTime']
|
|
88
|
+
}
|
|
89
|
+
fields = record['fields']
|
|
90
|
+
flattened_record.update(fields)
|
|
91
|
+
flattened_data.append(flattened_record)
|
|
92
|
+
|
|
93
|
+
return pd.DataFrame(flattened_data) # Create and return a DataFrame
|
|
94
|
+
|
|
95
|
+
def export(
|
|
96
|
+
self,
|
|
97
|
+
df: Union[pd.DataFrame, pl.DataFrame],
|
|
98
|
+
*args,
|
|
99
|
+
**kwargs
|
|
100
|
+
) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Not implemented yet. This method is intended to export data to Airtable.
|
|
103
|
+
"""
|
|
104
|
+
pass
|
mage_ai/io/base.py
CHANGED
mage_ai/io/bigquery.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import traceback
|
|
1
2
|
import uuid
|
|
2
3
|
from typing import Dict, List, Mapping, Union
|
|
3
4
|
|
|
4
5
|
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
5
7
|
from google.cloud.bigquery import (
|
|
6
8
|
Client,
|
|
7
9
|
LoadJobConfig,
|
|
@@ -399,7 +401,41 @@ WHERE table_id = '{table_name}'
|
|
|
399
401
|
# Clean column names
|
|
400
402
|
if type(df) is DataFrame:
|
|
401
403
|
df.columns = df.columns.str.replace(' ', '_')
|
|
404
|
+
table = None
|
|
405
|
+
try:
|
|
406
|
+
# Cast column types
|
|
407
|
+
table = self.client.get_table(table_id)
|
|
408
|
+
except Exception:
|
|
409
|
+
print(f'Table {table_id} does not exist.')
|
|
410
|
+
pass
|
|
402
411
|
|
|
412
|
+
if table is not None:
|
|
413
|
+
try:
|
|
414
|
+
timestamp_columns = [field.name for field in table.schema
|
|
415
|
+
if field.field_type == 'TIMESTAMP']
|
|
416
|
+
|
|
417
|
+
# Convert TIMESTAMP columns in DataFrame
|
|
418
|
+
for col in timestamp_columns:
|
|
419
|
+
if col in df.columns:
|
|
420
|
+
df[col] = pd.to_datetime(df[col])
|
|
421
|
+
except Exception:
|
|
422
|
+
print('Fail to cast column types in dataframe.')
|
|
423
|
+
traceback.print_exc()
|
|
424
|
+
if (
|
|
425
|
+
not config.schema and
|
|
426
|
+
config.write_disposition == WriteDisposition.WRITE_TRUNCATE
|
|
427
|
+
):
|
|
428
|
+
df_columns = df.columns.tolist()
|
|
429
|
+
config.schema = [
|
|
430
|
+
SchemaField(
|
|
431
|
+
field.name,
|
|
432
|
+
field.field_type,
|
|
433
|
+
mode=field.mode,
|
|
434
|
+
fields=field.fields,
|
|
435
|
+
)
|
|
436
|
+
for field in table.schema
|
|
437
|
+
if field.name in df_columns
|
|
438
|
+
]
|
|
403
439
|
return self.client.load_table_from_dataframe(df, table_id, job_config=config).result()
|
|
404
440
|
|
|
405
441
|
def execute(self, query_string: str, **kwargs) -> None:
|
mage_ai/io/config.py
CHANGED
|
@@ -16,6 +16,8 @@ class ConfigKey(StrEnum):
|
|
|
16
16
|
List of configuration settings for use with data IO clients.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
+
AIRTABLE_ACCESS_TOKEN = "AIRTABLE_ACCESS_TOKEN"
|
|
20
|
+
|
|
19
21
|
ALGOLIA_APP_ID = 'ALGOLIA_APP_ID'
|
|
20
22
|
ALGOLIA_API_KEY = 'ALGOLIA_API_KEY'
|
|
21
23
|
ALGOLIA_INDEX_NAME = 'ALGOLIA_INDEX_NAME'
|
|
@@ -78,6 +80,7 @@ class ConfigKey(StrEnum):
|
|
|
78
80
|
MYSQL_PASSWORD = 'MYSQL_PASSWORD'
|
|
79
81
|
MYSQL_PORT = 'MYSQL_PORT'
|
|
80
82
|
MYSQL_USER = 'MYSQL_USER'
|
|
83
|
+
MYSQL_ALLOW_LOCAL_INFILE = 'MYSQL_ALLOW_LOCAL_INFILE'
|
|
81
84
|
|
|
82
85
|
ORACLEDB_USER = 'ORACLEDB_USER'
|
|
83
86
|
ORACLEDB_PASSWORD = 'ORACLEDB_PASSWORD'
|
|
@@ -338,6 +341,7 @@ class VerboseConfigKey(StrEnum):
|
|
|
338
341
|
Config key headers for the verbose configuration file format.
|
|
339
342
|
"""
|
|
340
343
|
|
|
344
|
+
AIRTABLE = 'Airtable'
|
|
341
345
|
ALGOLIA = 'Algolia'
|
|
342
346
|
AWS = 'AWS'
|
|
343
347
|
BIGQUERY = 'BigQuery'
|
|
@@ -356,6 +360,7 @@ class VerboseConfigKey(StrEnum):
|
|
|
356
360
|
|
|
357
361
|
class ConfigFileLoader(BaseConfigLoader):
|
|
358
362
|
KEY_MAP = {
|
|
363
|
+
ConfigKey.AIRTABLE_ACCESS_TOKEN: VerboseConfigKey.AIRTABLE,
|
|
359
364
|
ConfigKey.ALGOLIA_APP_ID: (
|
|
360
365
|
VerboseConfigKey.ALGOLIA, 'app_id'),
|
|
361
366
|
ConfigKey.ALGOLIA_API_KEY: (
|