mage-ai 0.9.65__py3-none-any.whl → 0.9.67__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.

Files changed (242) hide show
  1. mage_ai/api/monitors/BaseMonitor.py +38 -16
  2. mage_ai/api/policies/BackfillPolicy.py +1 -0
  3. mage_ai/api/resources/OauthResource.py +13 -5
  4. mage_ai/api/resources/SessionResource.py +6 -4
  5. mage_ai/authentication/ldap.py +19 -9
  6. mage_ai/authentication/oauth/constants.py +8 -14
  7. mage_ai/authentication/oauth/utils.py +18 -6
  8. mage_ai/authentication/providers/active_directory.py +21 -16
  9. mage_ai/authentication/providers/azure_devops.py +18 -0
  10. mage_ai/authentication/providers/bitbucket.py +10 -9
  11. mage_ai/authentication/providers/constants.py +2 -0
  12. mage_ai/authentication/providers/ghe.py +5 -9
  13. mage_ai/authentication/providers/gitlab.py +6 -9
  14. mage_ai/authentication/providers/google.py +9 -6
  15. mage_ai/authentication/providers/oidc.py +6 -4
  16. mage_ai/authentication/providers/okta.py +9 -6
  17. mage_ai/cluster_manager/kubernetes/workload_manager.py +10 -0
  18. mage_ai/cluster_manager/workspace/base.py +6 -1
  19. mage_ai/cluster_manager/workspace/kubernetes.py +3 -0
  20. mage_ai/data_preparation/decorators.py +15 -0
  21. mage_ai/data_preparation/executors/streaming_pipeline_executor.py +22 -12
  22. mage_ai/data_preparation/git/__init__.py +10 -1
  23. mage_ai/data_preparation/git/api.py +3 -0
  24. mage_ai/data_preparation/git/clients/azure_devops.py +106 -0
  25. mage_ai/data_preparation/git/clients/base.py +6 -0
  26. mage_ai/data_preparation/git/clients/gitlab.py +3 -2
  27. mage_ai/data_preparation/git/utils.py +31 -29
  28. mage_ai/data_preparation/models/block/__init__.py +27 -18
  29. mage_ai/data_preparation/models/block/dbt/block_sql.py +164 -0
  30. mage_ai/data_preparation/models/block/dynamic/variables.py +1 -2
  31. mage_ai/data_preparation/models/pipeline.py +3 -3
  32. mage_ai/data_preparation/models/triggers/__init__.py +6 -1
  33. mage_ai/data_preparation/preferences.py +42 -37
  34. mage_ai/data_preparation/repo_manager.py +21 -0
  35. mage_ai/data_preparation/storage/gcs_storage.py +27 -2
  36. mage_ai/data_preparation/storage/local_storage.py +18 -3
  37. mage_ai/data_preparation/storage/s3_storage.py +7 -2
  38. mage_ai/data_preparation/templates/data_loaders/streaming/generic_python.py +23 -0
  39. mage_ai/data_preparation/templates/main/metadata.yaml +6 -0
  40. mage_ai/data_preparation/templates/template.py +6 -2
  41. mage_ai/data_preparation/variable_manager.py +2 -1
  42. mage_ai/io/base.py +3 -0
  43. mage_ai/io/bigquery.py +2 -0
  44. mage_ai/io/export_utils.py +14 -9
  45. mage_ai/io/mssql.py +104 -25
  46. mage_ai/io/mysql.py +10 -9
  47. mage_ai/io/oracledb.py +14 -2
  48. mage_ai/io/postgres.py +3 -0
  49. mage_ai/io/sql.py +14 -6
  50. mage_ai/io/trino.py +10 -8
  51. mage_ai/orchestration/db/migrations/versions/90d978a8aef8_update_unique_constraint_for_secret.py +11 -5
  52. mage_ai/orchestration/db/models/schedules.py +25 -1
  53. mage_ai/orchestration/db/models/schedules_project_platform.py +24 -1
  54. mage_ai/orchestration/job_manager.py +6 -1
  55. mage_ai/orchestration/pipeline_scheduler_original.py +16 -10
  56. mage_ai/server/constants.py +1 -1
  57. mage_ai/server/file_observer.py +10 -0
  58. mage_ai/server/frontend_dist/404.html +2 -2
  59. mage_ai/server/frontend_dist/_next/static/chunks/{1557-a754b04510d50b80.js → 1557-01f0843dc6ac4971.js} +1 -1
  60. mage_ai/server/frontend_dist/_next/static/chunks/9440-4069842b90d4b801.js +1 -0
  61. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-1c1ffd928f5a00f7.js +1 -0
  62. mage_ai/server/frontend_dist/_next/static/chunks/pages/index-b7b8695a7f9efde2.js +1 -0
  63. mage_ai/server/frontend_dist/_next/static/chunks/pages/oauth-30e34ee15d410331.js +1 -0
  64. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ff4bd7a8ec3bab40.js +1 -0
  65. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-aaf393c86fc1bda3.js +1 -0
  66. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{edit-8d32ac7e8f023779.js → edit-36377e679da2cd91.js} +1 -1
  67. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/logs-3f5c14076ddde20e.js +1 -0
  68. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-fe08120d9a6fb1b0.js → triggers-f508c2f261297724.js} +1 -1
  69. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-bcdb4ad41dd4c7d5.js → frontend_dist/_next/static/chunks/pages/pipelines-f99e99aa8f45529c.js} +1 -1
  70. mage_ai/server/frontend_dist/_next/static/chunks/pages/{sign-in-19b36600d908b711.js → sign-in-7d38b2f7c3e918a1.js} +1 -1
  71. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-5753fac7c1bfdc88.js +1 -0
  72. mage_ai/server/frontend_dist/_next/static/chunks/{webpack-ac7fdc472bedf682.js → webpack-d079359c241db804.js} +1 -1
  73. mage_ai/server/frontend_dist/_next/static/{ZMrJfDouIX5AMb_RteRbL → vPsMu6Fi2zrHaf2fRXKRO}/_buildManifest.js +1 -1
  74. mage_ai/server/frontend_dist/block-layout.html +2 -2
  75. mage_ai/server/frontend_dist/compute.html +2 -2
  76. mage_ai/server/frontend_dist/files.html +2 -2
  77. mage_ai/server/frontend_dist/global-data-products/[...slug].html +2 -2
  78. mage_ai/server/frontend_dist/global-data-products.html +2 -2
  79. mage_ai/server/frontend_dist/global-hooks/[...slug].html +2 -2
  80. mage_ai/server/frontend_dist/global-hooks.html +2 -2
  81. mage_ai/server/frontend_dist/index.html +2 -2
  82. mage_ai/server/frontend_dist/manage/files.html +2 -2
  83. mage_ai/server/frontend_dist/manage/settings.html +2 -2
  84. mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
  85. mage_ai/server/frontend_dist/manage/users/new.html +2 -2
  86. mage_ai/server/frontend_dist/manage/users.html +2 -2
  87. mage_ai/server/frontend_dist/manage.html +2 -2
  88. mage_ai/server/frontend_dist/oauth.html +2 -2
  89. mage_ai/server/frontend_dist/overview.html +2 -2
  90. mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
  91. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  92. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
  93. mage_ai/server/frontend_dist/pipelines/[pipeline]/dashboard.html +2 -2
  94. mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
  95. mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
  96. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  97. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  98. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
  99. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
  100. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
  101. mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
  102. mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
  103. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  104. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
  105. mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
  106. mage_ai/server/frontend_dist/pipelines.html +2 -2
  107. mage_ai/server/frontend_dist/platform/global-hooks/[...slug].html +2 -2
  108. mage_ai/server/frontend_dist/platform/global-hooks.html +2 -2
  109. mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
  110. mage_ai/server/frontend_dist/settings/platform/preferences.html +2 -2
  111. mage_ai/server/frontend_dist/settings/platform/settings.html +2 -2
  112. mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +2 -2
  113. mage_ai/server/frontend_dist/settings/workspace/permissions.html +2 -2
  114. mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
  115. mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +2 -2
  116. mage_ai/server/frontend_dist/settings/workspace/roles.html +2 -2
  117. mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
  118. mage_ai/server/frontend_dist/settings/workspace/users/[...slug].html +2 -2
  119. mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
  120. mage_ai/server/frontend_dist/settings.html +2 -2
  121. mage_ai/server/frontend_dist/sign-in.html +2 -2
  122. mage_ai/server/frontend_dist/templates/[...slug].html +2 -2
  123. mage_ai/server/frontend_dist/templates.html +2 -2
  124. mage_ai/server/frontend_dist/terminal.html +2 -2
  125. mage_ai/server/frontend_dist/test.html +2 -2
  126. mage_ai/server/frontend_dist/triggers.html +2 -2
  127. mage_ai/server/frontend_dist/version-control.html +2 -2
  128. mage_ai/server/frontend_dist_base_path_template/404.html +2 -2
  129. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{1557-a754b04510d50b80.js → 1557-01f0843dc6ac4971.js} +1 -1
  130. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9440-4069842b90d4b801.js +1 -0
  131. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-1c1ffd928f5a00f7.js +1 -0
  132. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-b7b8695a7f9efde2.js +1 -0
  133. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/oauth-30e34ee15d410331.js +1 -0
  134. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ff4bd7a8ec3bab40.js +1 -0
  135. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-aaf393c86fc1bda3.js +1 -0
  136. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{edit-8d32ac7e8f023779.js → edit-36377e679da2cd91.js} +1 -1
  137. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/logs-3f5c14076ddde20e.js +1 -0
  138. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-fe08120d9a6fb1b0.js → triggers-f508c2f261297724.js} +1 -1
  139. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines-bcdb4ad41dd4c7d5.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-f99e99aa8f45529c.js} +1 -1
  140. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{sign-in-19b36600d908b711.js → sign-in-7d38b2f7c3e918a1.js} +1 -1
  141. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-5753fac7c1bfdc88.js +1 -0
  142. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{webpack-481689d9989710cd.js → webpack-68c003fb6a175cd7.js} +1 -1
  143. mage_ai/server/frontend_dist_base_path_template/_next/static/{QYwFH4sievaq5XyUjRriy → khKiaJtwrslgMmp4YSa1f}/_buildManifest.js +1 -1
  144. mage_ai/server/frontend_dist_base_path_template/block-layout.html +2 -2
  145. mage_ai/server/frontend_dist_base_path_template/compute.html +2 -2
  146. mage_ai/server/frontend_dist_base_path_template/files.html +2 -2
  147. mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +2 -2
  148. mage_ai/server/frontend_dist_base_path_template/global-data-products.html +2 -2
  149. mage_ai/server/frontend_dist_base_path_template/global-hooks/[...slug].html +2 -2
  150. mage_ai/server/frontend_dist_base_path_template/global-hooks.html +2 -2
  151. mage_ai/server/frontend_dist_base_path_template/index.html +2 -2
  152. mage_ai/server/frontend_dist_base_path_template/manage/files.html +2 -2
  153. mage_ai/server/frontend_dist_base_path_template/manage/settings.html +2 -2
  154. mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +2 -2
  155. mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +2 -2
  156. mage_ai/server/frontend_dist_base_path_template/manage/users.html +2 -2
  157. mage_ai/server/frontend_dist_base_path_template/manage.html +2 -2
  158. mage_ai/server/frontend_dist_base_path_template/oauth.html +2 -2
  159. mage_ai/server/frontend_dist_base_path_template/overview.html +2 -2
  160. mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +2 -2
  161. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  162. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +2 -2
  163. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +2 -2
  164. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +2 -2
  165. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +2 -2
  166. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  167. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  168. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +2 -2
  169. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +2 -2
  170. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +2 -2
  171. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +2 -2
  172. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +2 -2
  173. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  174. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +2 -2
  175. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +2 -2
  176. mage_ai/server/frontend_dist_base_path_template/pipelines.html +2 -2
  177. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks/[...slug].html +2 -2
  178. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks.html +2 -2
  179. mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +2 -2
  180. mage_ai/server/frontend_dist_base_path_template/settings/platform/preferences.html +2 -2
  181. mage_ai/server/frontend_dist_base_path_template/settings/platform/settings.html +2 -2
  182. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +2 -2
  183. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +2 -2
  184. mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +2 -2
  185. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +2 -2
  186. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +2 -2
  187. mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +2 -2
  188. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +2 -2
  189. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +2 -2
  190. mage_ai/server/frontend_dist_base_path_template/settings.html +2 -2
  191. mage_ai/server/frontend_dist_base_path_template/sign-in.html +2 -2
  192. mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +2 -2
  193. mage_ai/server/frontend_dist_base_path_template/templates.html +2 -2
  194. mage_ai/server/frontend_dist_base_path_template/terminal.html +2 -2
  195. mage_ai/server/frontend_dist_base_path_template/test.html +2 -2
  196. mage_ai/server/frontend_dist_base_path_template/triggers.html +2 -2
  197. mage_ai/server/frontend_dist_base_path_template/version-control.html +2 -2
  198. mage_ai/server/server.py +117 -87
  199. mage_ai/server/utils/output_display.py +6 -1
  200. mage_ai/services/aws/s3/s3.py +8 -2
  201. mage_ai/services/slack/slack.py +8 -8
  202. mage_ai/settings/__init__.py +36 -186
  203. mage_ai/settings/backends.py +95 -0
  204. mage_ai/settings/keys/__init__.py +1 -0
  205. mage_ai/settings/keys/auth.py +76 -0
  206. mage_ai/settings/server.py +187 -0
  207. mage_ai/shared/io.py +2 -2
  208. mage_ai/shared/logger.py +12 -6
  209. mage_ai/streaming/sources/base_python.py +30 -0
  210. mage_ai/streaming/sources/source_factory.py +25 -0
  211. mage_ai/tests/api/endpoints/test_oauths.py +13 -5
  212. mage_ai/tests/api/operations/test_operations.py +2 -2
  213. mage_ai/tests/api/operations/test_sessions.py +83 -48
  214. mage_ai/tests/authentication/oauth/test_utils.py +56 -6
  215. mage_ai/tests/authentication/providers/test_active_directory.py +9 -15
  216. mage_ai/tests/data_preparation/models/test_block.py +39 -2
  217. mage_ai/tests/orchestration/db/models/test_schedules.py +33 -1
  218. {mage_ai-0.9.65.dist-info → mage_ai-0.9.67.dist-info}/METADATA +2 -1
  219. {mage_ai-0.9.65.dist-info → mage_ai-0.9.67.dist-info}/RECORD +225 -217
  220. {mage_ai-0.9.65.dist-info → mage_ai-0.9.67.dist-info}/WHEEL +1 -1
  221. mage_ai/server/frontend_dist/_next/static/chunks/9440-2bcbdc765ed82062.js +0 -1
  222. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-2ae1d919333f01fe.js +0 -1
  223. mage_ai/server/frontend_dist/_next/static/chunks/pages/index-64851458dde54ad9.js +0 -1
  224. mage_ai/server/frontend_dist/_next/static/chunks/pages/oauth-abe5ba687cb93509.js +0 -1
  225. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-22e49726eeed16ae.js +0 -1
  226. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-c74507dce89b41a2.js +0 -1
  227. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/logs-cf656cbe37ecaacc.js +0 -1
  228. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-690206d30d8b412b.js +0 -1
  229. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9440-2bcbdc765ed82062.js +0 -1
  230. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-2ae1d919333f01fe.js +0 -1
  231. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-64851458dde54ad9.js +0 -1
  232. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/oauth-abe5ba687cb93509.js +0 -1
  233. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-22e49726eeed16ae.js +0 -1
  234. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-c74507dce89b41a2.js +0 -1
  235. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/logs-cf656cbe37ecaacc.js +0 -1
  236. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-690206d30d8b412b.js +0 -1
  237. mage_ai/settings/sso.py +0 -27
  238. /mage_ai/server/frontend_dist/_next/static/{ZMrJfDouIX5AMb_RteRbL → vPsMu6Fi2zrHaf2fRXKRO}/_ssgManifest.js +0 -0
  239. /mage_ai/server/frontend_dist_base_path_template/_next/static/{QYwFH4sievaq5XyUjRriy → khKiaJtwrslgMmp4YSa1f}/_ssgManifest.js +0 -0
  240. {mage_ai-0.9.65.dist-info → mage_ai-0.9.67.dist-info}/LICENSE +0 -0
  241. {mage_ai-0.9.65.dist-info → mage_ai-0.9.67.dist-info}/entry_points.txt +0 -0
  242. {mage_ai-0.9.65.dist-info → mage_ai-0.9.67.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ from pathlib import Path
5
5
  from typing import Any, Dict, List, Optional, Tuple, Union
6
6
 
7
7
  import pandas as pd
8
+ import simplejson
8
9
  from jinja2 import Template
9
10
 
10
11
  from mage_ai.data_preparation.models.block import Block
@@ -27,6 +28,8 @@ from mage_ai.settings.utils import base_repo_path
27
28
  from mage_ai.shared.files import find_file_from_another_file_path
28
29
  from mage_ai.shared.hash import merge_dict
29
30
  from mage_ai.shared.path_fixer import add_absolute_path
31
+ from mage_ai.shared.strings import remove_extension_from_filename
32
+ from mage_ai.shared.utils import clean_name
30
33
 
31
34
 
32
35
  class DBTBlockSQL(DBTBlock, ProjectPlatformAccessible):
@@ -136,6 +139,167 @@ class DBTBlockSQL(DBTBlock, ProjectPlatformAccessible):
136
139
 
137
140
  return search_paths.get(node_path_type_part)
138
141
 
142
+ def upstream_dbt_blocks(self, read_only=False) -> List['DBTBlockSQL']:
143
+ """
144
+ Get an up to date list, which represents the upstream dbt graph.
145
+ It is using `dbt list` to generate the list.
146
+ Args:
147
+ read_only (bool):
148
+ If True it does not read the Blocks from the model. Defaults to False
149
+ Returns:
150
+ List[DBTBlockSQL]: THe upstream dbt graph as DBTBlocksSQL objects
151
+ """
152
+ # Get upstream nodes via dbt list
153
+ with Profiles(
154
+ self.project_path,
155
+ self.pipeline.variables if self.pipeline else {},
156
+ ) as profiles:
157
+ try:
158
+ args = [
159
+ 'list',
160
+ # project-dir
161
+ # /home/src/default_repo/default_platform/default_repo/dbt/demo
162
+ '--project-dir', self.project_path,
163
+ '--profiles-dir', str(profiles.profiles_dir),
164
+ '--select', '+' + Path(self.file_path).stem,
165
+ '--output', 'json',
166
+ '--output-keys', 'unique_id original_file_path depends_on',
167
+ '--resource-type', 'model',
168
+ '--resource-type', 'snapshot'
169
+ ]
170
+ res = DBTCli().invoke(args)
171
+
172
+ if res:
173
+ nodes_init = [simplejson.loads(node) for node in res.result]
174
+ else:
175
+ return []
176
+ except Exception as err:
177
+ print(f'[ERROR] DBTBlockSQL.upstream_dbt_blocks: {err}.')
178
+ return [
179
+ self.build_dbt_block(
180
+ block_class=DBTBlock,
181
+ block_dict=dict(
182
+ block_type=self.type,
183
+ configuration=self.configuration,
184
+ language=self.language,
185
+ name=self.uuid,
186
+ pipeline=self.pipeline,
187
+ uuid=self.uuid,
188
+ ),
189
+ hydrate_configuration=False,
190
+ )
191
+ ]
192
+
193
+ # transform List into dict and remove unnecessary fields
194
+ file_path = self.file_path
195
+ path_parts = file_path.split(os.sep)
196
+ project_dir = path_parts[0]
197
+
198
+ nodes_default = {
199
+ node['unique_id']: {
200
+ # file_path:
201
+ # default_repo/dbt/demo/models/example/model.sql
202
+ # default_platform/default_repo/dbt/demo/models/example/model.sql
203
+ 'file_path': os.path.join(project_dir, node['original_file_path']),
204
+ 'original_file_path': node['original_file_path'],
205
+ 'upstream_nodes': set(node['depends_on']['nodes'])
206
+ }
207
+ for node in nodes_init
208
+ }
209
+
210
+ nodes = self.hydrate_dbt_nodes(nodes_default, nodes_init)
211
+
212
+ # calculate downstream_nodes
213
+ for unique_id, node in nodes.items():
214
+ for upstream_node in node['upstream_nodes']:
215
+ if nodes.get(upstream_node):
216
+ downstream_nodes = nodes[upstream_node].get('downstream_nodes', set())
217
+ downstream_nodes.add(unique_id)
218
+ nodes[upstream_node]['downstream_nodes'] = downstream_nodes
219
+
220
+ # map dbt unique_id to mage uuid
221
+ uuids_default = {
222
+ unique_id: clean_name(
223
+ remove_extension_from_filename(node['file_path']),
224
+ allow_characters=[os.sep]
225
+ )
226
+ for unique_id, node in nodes.items()
227
+ }
228
+ uuids = self.node_uuids_mapping(uuids_default, nodes)
229
+
230
+ # replace dbt unique_ids with mage uuids
231
+ nodes = {
232
+ uuids[unique_id]: {
233
+ 'file_path': node['file_path'],
234
+ 'original_file_path': node['original_file_path'],
235
+ 'upstream_nodes': {
236
+ uuids[upstream_node]
237
+ for upstream_node in node.get('upstream_nodes', set())
238
+ if uuids.get(upstream_node)
239
+ },
240
+ 'downstream_nodes': {
241
+ uuids[downstream_node]
242
+ for downstream_node in node.get('downstream_nodes', set())
243
+ if uuids.get(downstream_node)
244
+ }
245
+ }
246
+ for unique_id, node in nodes.items()
247
+ }
248
+
249
+ # create dict of blocks
250
+ blocks = {}
251
+ for uuid, node in nodes.items():
252
+ block = None
253
+ if not read_only:
254
+ if uuid == self.uuid:
255
+ block = self
256
+ elif self.pipeline:
257
+ block = self.pipeline.get_block(
258
+ uuid,
259
+ self.type,
260
+ )
261
+ # if not found create the block
262
+ block = block or self.build_dbt_block(
263
+ block_class=DBTBlock,
264
+ block_dict=dict(
265
+ block_type=self.type,
266
+ configuration=dict(file_path=node['file_path']),
267
+ language=self.language,
268
+ name=uuid,
269
+ pipeline=self.pipeline,
270
+ uuid=uuid,
271
+ ),
272
+ node=node,
273
+ )
274
+ # reset upstream dbt blocks
275
+ block.upstream_blocks = [
276
+ block
277
+ for block in block.upstream_blocks
278
+ # there must not be any other upstream dbt blocks that are not part of
279
+ # the upstream nodes, therefore delete all upstream dbt blocks
280
+ if not isinstance(block, DBTBlockSQL)
281
+ ]
282
+ # reset downstream dbt blocks
283
+ block.downstream_blocks = [
284
+ block
285
+ for block in block.downstream_blocks
286
+ # remove all downstream dbt block, which are part of the upstream nodes,
287
+ # in order to fix errors
288
+ if block.uuid not in nodes.keys()
289
+ ]
290
+ blocks[uuid] = block
291
+
292
+ # Update upstream and downstream dbt blocks
293
+ for uuid, node in nodes.items():
294
+ for upstream_node in node.get('upstream_nodes', set()):
295
+ blocks[uuid].upstream_blocks.append(blocks[upstream_node])
296
+ for downstream_node in node.get('downstream_nodes', set()):
297
+ blocks[uuid].downstream_blocks.append(blocks[downstream_node])
298
+
299
+ # transform into list
300
+ blocks = [block for _, block in blocks.items()]
301
+ return blocks
302
+
139
303
  def _execute_block(
140
304
  self,
141
305
  outputs_from_input_vars,
@@ -522,9 +522,8 @@ def fetch_input_variables_for_dynamic_upstream_blocks(
522
522
  global_vars=global_vars,
523
523
  **kwargs,
524
524
  )
525
-
526
525
  input_vars.append(ir[0])
527
- kwargs_vars.append(kr[0])
526
+ kwargs_vars.append(kr[0] if len(kr) > 0 else None)
528
527
  upstream_block_uuids.append(up[0])
529
528
 
530
529
  return input_vars, kwargs_vars, upstream_block_uuids
@@ -1443,7 +1443,7 @@ class Pipeline:
1443
1443
  block['downstream_blocks'] = hierarchy_list[b_index]['downstream_blocks']
1444
1444
 
1445
1445
  # dump new config information back in temp folder
1446
- with open(config_zip_path, 'w') as pipeline_config:
1446
+ with open(config_zip_path, 'w', encoding='utf-8') as pipeline_config:
1447
1447
  yaml.dump(config, pipeline_config)
1448
1448
 
1449
1449
  return files_to_be_written, config
@@ -2141,12 +2141,12 @@ class Pipeline:
2141
2141
  content = yaml.dump(pipeline_dict)
2142
2142
 
2143
2143
  test_path = f'{self.config_path}.test'
2144
- async with aiofiles.open(test_path, mode='w') as fp:
2144
+ async with aiofiles.open(test_path, mode='w', encoding='utf-8') as fp:
2145
2145
  await fp.write(content)
2146
2146
 
2147
2147
  if os.path.isfile(test_path):
2148
2148
  success = True
2149
- with open(test_path, mode='r') as fp:
2149
+ with open(test_path, mode='r', encoding='utf-8') as fp:
2150
2150
  try:
2151
2151
  yaml.full_load(fp)
2152
2152
  except yaml.scanner.ScannerError:
@@ -69,6 +69,7 @@ class Trigger(BaseConfig):
69
69
  sla: int = None # in seconds
70
70
  settings: Dict = field(default_factory=dict)
71
71
  envs: List = field(default_factory=list)
72
+ repo_path: str = None
72
73
 
73
74
  def __post_init__(self):
74
75
  if self.schedule_type and type(self.schedule_type) is str:
@@ -163,6 +164,7 @@ def build_triggers(
163
164
  trigger_configs: Dict,
164
165
  pipeline_uuid: str = None,
165
166
  raise_exception: bool = False,
167
+ repo_path: str = None,
166
168
  ) -> List[Trigger]:
167
169
  triggers = []
168
170
  for trigger_config in trigger_configs:
@@ -170,6 +172,8 @@ def build_triggers(
170
172
  trigger_config['pipeline_uuid'] = pipeline_uuid
171
173
  try:
172
174
  trigger = Trigger.load(config=trigger_config)
175
+ if trigger.repo_path is None:
176
+ trigger.repo_path = repo_path
173
177
 
174
178
  # Add flag to settings so frontend can detect triggers with invalid cron expressions
175
179
  if not trigger.has_valid_schedule_interval:
@@ -194,7 +198,8 @@ def load_trigger_configs(
194
198
  yaml_config = yaml.safe_load(content) or {}
195
199
  trigger_configs = yaml_config.get('triggers') or {}
196
200
 
197
- return build_triggers(trigger_configs, pipeline_uuid, raise_exception)
201
+ return build_triggers(
202
+ trigger_configs, pipeline_uuid, raise_exception, repo_path=get_repo_path())
198
203
 
199
204
 
200
205
  def add_or_update_trigger_for_pipeline_and_persist(
@@ -6,51 +6,49 @@ import yaml
6
6
 
7
7
  from mage_ai.data_preparation.models.constants import PREFERENCES_FILE
8
8
  from mage_ai.orchestration.db.models.oauth import User
9
- from mage_ai.settings import get_bool_value
9
+ from mage_ai.settings import get_bool_value, get_settings_value
10
+ from mage_ai.settings.keys import (
11
+ GIT_AUTH_TYPE,
12
+ GIT_BRANCH,
13
+ GIT_EMAIL,
14
+ GIT_ENABLE_GIT_INTEGRATION,
15
+ GIT_OVERWRITE_WITH_PROJECT_SETTINGS,
16
+ GIT_REPO_LINK,
17
+ GIT_REPO_PATH,
18
+ GIT_SYNC_ON_EXECUTOR_START,
19
+ GIT_SYNC_ON_PIPELINE_RUN,
20
+ GIT_SYNC_ON_START,
21
+ GIT_SYNC_SUBMODULES,
22
+ GIT_USERNAME,
23
+ )
10
24
  from mage_ai.settings.repo import get_repo_path
11
25
  from mage_ai.shared.hash import merge_dict
12
26
 
13
- # Git environment variables
14
- GIT_REPO_LINK_VAR = 'GIT_REPO_LINK'
15
- GIT_REPO_PATH_VAR = 'GIT_REPO_PATH'
16
- GIT_USERNAME_VAR = 'GIT_USERNAME'
17
- GIT_EMAIL_VAR = 'GIT_EMAIL'
18
- GIT_AUTH_TYPE_VAR = 'GIT_AUTH_TYPE'
19
- GIT_BRANCH_VAR = 'GIT_BRANCH'
20
- GIT_SYNC_ON_PIPELINE_RUN_VAR = 'GIT_SYNC_ON_PIPELINE_RUN'
21
- GIT_SYNC_ON_START_VAR = 'GIT_SYNC_ON_START'
22
- GIT_SYNC_ON_EXECUTOR_START_VAR = 'GIT_SYNC_ON_EXECUTOR_START'
23
- GIT_SYNC_SUBMODULES_VAR = 'GIT_SYNC_SUBMODULES'
24
-
25
- GIT_ENABLE_GIT_INTEGRATION_VAR = 'GIT_ENABLE_GIT_INTEGRATION'
26
- GIT_OVERWRITE_WITH_PROJECT_SETTINGS_VAR = 'GIT_OVERWRITE_WITH_PROJECT_SETTINGS'
27
-
28
27
  ENV_VAR_TO_CONFIG_KEY = {
29
- GIT_REPO_LINK_VAR: 'remote_repo_link',
30
- GIT_REPO_PATH_VAR: 'repo_path',
31
- GIT_USERNAME_VAR: 'username',
32
- GIT_EMAIL_VAR: 'email',
33
- GIT_AUTH_TYPE_VAR: 'auth_type',
34
- GIT_BRANCH_VAR: 'branch',
35
- GIT_SYNC_ON_PIPELINE_RUN_VAR: 'sync_on_pipeline_run',
36
- GIT_SYNC_ON_START_VAR: 'sync_on_start',
37
- GIT_SYNC_ON_EXECUTOR_START_VAR: 'sync_on_executor_start',
38
- GIT_SYNC_SUBMODULES_VAR: 'sync_submodules',
28
+ GIT_REPO_LINK: 'remote_repo_link',
29
+ GIT_REPO_PATH: 'repo_path',
30
+ GIT_USERNAME: 'username',
31
+ GIT_EMAIL: 'email',
32
+ GIT_AUTH_TYPE: 'auth_type',
33
+ GIT_BRANCH: 'branch',
34
+ GIT_SYNC_ON_PIPELINE_RUN: 'sync_on_pipeline_run',
35
+ GIT_SYNC_ON_START: 'sync_on_start',
36
+ GIT_SYNC_ON_EXECUTOR_START: 'sync_on_executor_start',
37
+ GIT_SYNC_SUBMODULES: 'sync_submodules',
39
38
  }
40
39
 
41
40
  BOOLEAN_ENV_VARS = set(
42
41
  [
43
- GIT_SYNC_ON_PIPELINE_RUN_VAR,
44
- GIT_SYNC_ON_START_VAR,
45
- GIT_SYNC_ON_EXECUTOR_START_VAR,
46
- GIT_SYNC_SUBMODULES_VAR,
47
- GIT_ENABLE_GIT_INTEGRATION_VAR,
42
+ GIT_SYNC_ON_PIPELINE_RUN,
43
+ GIT_SYNC_ON_START,
44
+ GIT_SYNC_ON_EXECUTOR_START,
45
+ GIT_SYNC_SUBMODULES,
46
+ GIT_ENABLE_GIT_INTEGRATION,
48
47
  ]
49
48
  )
50
49
 
51
50
 
52
- def get_value_for_sync_config(env_var) -> bool:
53
- value = os.getenv(env_var)
51
+ def get_value_for_sync_config(env_var, value) -> bool:
54
52
  return get_bool_value(value) if env_var in BOOLEAN_ENV_VARS else value
55
53
 
56
54
 
@@ -68,21 +66,28 @@ def build_sync_config(project_sync_config: Dict) -> Dict:
68
66
  if v is not None and (not isinstance(v, str) or v)
69
67
  }
70
68
 
71
- if any([k in os.environ for k in ENV_VAR_TO_CONFIG_KEY]):
69
+ # Read settings from settings backend
70
+ config_from_settings = {
71
+ k: get_settings_value(k)
72
+ for k in ENV_VAR_TO_CONFIG_KEY.keys()
73
+ }
74
+
75
+ if any([v is not None for v in config_from_settings.values()]):
76
+ # Map sync config keys to settings values
72
77
  sync_config = {
73
- v: get_value_for_sync_config(k)
78
+ v: get_value_for_sync_config(k, config_from_settings.get(k))
74
79
  for k, v in ENV_VAR_TO_CONFIG_KEY.items()
75
80
  }
76
81
 
77
82
  # If the GIT_OVERWRITE_WITH_PROJECT_SETTINGS environment variable is set to true,
78
83
  # overwrite the sync_config with the config from the project preferences file.
79
- if get_bool_value(os.getenv(GIT_OVERWRITE_WITH_PROJECT_SETTINGS_VAR)):
84
+ if get_bool_value(get_settings_value(GIT_OVERWRITE_WITH_PROJECT_SETTINGS)):
80
85
  sync_config = merge_dict(sync_config, project_sync_config)
81
86
  else:
82
87
  sync_config = project_sync_config
83
88
 
84
89
  # Set the enable_git_integration field from environment variables
85
- enable_git_integration = os.getenv(GIT_ENABLE_GIT_INTEGRATION_VAR)
90
+ enable_git_integration = get_settings_value(GIT_ENABLE_GIT_INTEGRATION)
86
91
  if enable_git_integration is not None:
87
92
  sync_config['enable_git_integration'] = get_bool_value(enable_git_integration)
88
93
 
@@ -1,3 +1,5 @@
1
+ import json
2
+ import logging
1
3
  import os
2
4
  import traceback
3
5
  import uuid
@@ -11,6 +13,7 @@ from jinja2 import Template
11
13
 
12
14
  from mage_ai.cluster_manager.constants import ClusterType
13
15
  from mage_ai.data_preparation.templates.utils import copy_template_directory
16
+ from mage_ai.settings import INITIAL_METADATA, settings
14
17
  from mage_ai.settings.repo import DEFAULT_MAGE_DATA_DIR, MAGE_DATA_DIR_ENV_VAR
15
18
  from mage_ai.settings.repo import get_data_dir as get_data_dir_new
16
19
  from mage_ai.settings.repo import get_metadata_path
@@ -25,6 +28,8 @@ yml.preserve_quotes = True
25
28
  yml.width = float('inf')
26
29
  yml.indent(mapping=2, sequence=2, offset=0)
27
30
 
31
+ logger = logging.getLogger(__name__)
32
+
28
33
 
29
34
  class ProjectType(str, Enum):
30
35
  MAIN = 'main'
@@ -64,9 +69,12 @@ class RepoConfig:
64
69
  self.ldap_config = None
65
70
  self.s3_bucket = None
66
71
  self.s3_path_prefix = None
72
+ self.settings_backend = None
67
73
  self.logging_config = None
68
74
  self.variables_dir = None
69
75
  self.variables_retention_period = None
76
+ self.workspace_initial_metadata = None
77
+ self.workspace_shared_config = None
70
78
 
71
79
  from mage_ai.data_preparation.shared.utils import get_template_vars
72
80
  try:
@@ -125,6 +133,7 @@ class RepoConfig:
125
133
  self.pipelines = repo_config.get('pipelines')
126
134
  self.retry_config = repo_config.get('retry_config')
127
135
  self.workspace_config_defaults = repo_config.get('workspace_config_defaults')
136
+ self.workspace_initial_metadata = repo_config.get('workspace_initial_metadata')
128
137
 
129
138
  self.ldap_config = repo_config.get('ldap_config')
130
139
 
@@ -136,6 +145,8 @@ class RepoConfig:
136
145
  self.s3_bucket = path_parts.pop(0)
137
146
  self.s3_path_prefix = '/'.join(path_parts)
138
147
 
148
+ self.settings_backend = repo_config.get('settings_backend', dict())
149
+
139
150
  self.logging_config = repo_config.get('logging_config', dict())
140
151
 
141
152
  self.variables_retention_period = repo_config.get('variables_retention_period')
@@ -233,6 +244,11 @@ def init_repo(
233
244
  raise FileExistsError(f'Repository {repo_path} already exists')
234
245
 
235
246
  new_config = dict()
247
+ if INITIAL_METADATA:
248
+ try:
249
+ new_config = json.loads(INITIAL_METADATA)
250
+ except Exception:
251
+ logger.exception('Error loading initial metadata.')
236
252
  if project_type == ProjectType.MAIN:
237
253
  copy_template_directory('main', repo_path)
238
254
  new_config.update(
@@ -300,6 +316,11 @@ def set_project_uuid_from_metadata() -> None:
300
316
  project_uuid = config.get('project_uuid')
301
317
 
302
318
 
319
+ def update_settings_on_metadata_change() -> None:
320
+ repo_config = get_repo_config(root_project=True)
321
+ settings.set_settings_backend(**repo_config.settings_backend)
322
+
323
+
303
324
  def init_project_uuid(overwrite_uuid: str = None, root_project: bool = False) -> None:
304
325
  """
305
326
  Initialize the project_uuid constant. The project_uuid constant is used throughout
@@ -29,11 +29,36 @@ class GCSStorage(BaseStorage):
29
29
  path += '/'
30
30
  return self.path_exists(path)
31
31
 
32
- def listdir(self, path: str, suffix: str = None) -> List[str]:
32
+ def listdir(
33
+ self,
34
+ path: str,
35
+ suffix: str = None,
36
+ max_results: int = None,
37
+ ) -> List[str]:
33
38
  if not path.endswith('/'):
34
39
  path += '/'
35
40
  path = gcs_url_path(path)
36
- blobs = self.bucket.list_blobs(prefix=path)
41
+
42
+ """
43
+ NOTE: The number of results returned by the GCS bucket list_blobs method may
44
+ be lower than the number entered for the max_results argument, since this
45
+ method recursively fetches files inside of subdirectories at the path
46
+ specified, not just the directories themselves at the top level of the path.
47
+
48
+ As a result, when fetching block output folders in a remote variables directory,
49
+ the json or parquet files inside of the block output folders (e.g. the "type.json"
50
+ file in folder "output_0") are counted towards the "max_results" limit. If max_results
51
+ is set to 10, less than 10 output folders may be returned due to there being multiple
52
+ json or other files in each output folder. We do not increase the max_results or
53
+ include a "delimiter" argument because it would increase load times to unacceptable levels.
54
+
55
+ Example block output path in GCS:
56
+ gs://mage_demo/pipelines/example_pipeline/.variables/example_block/output_0/
57
+ """
58
+ blobs = self.bucket.list_blobs(
59
+ prefix=path,
60
+ max_results=max_results,
61
+ )
37
62
  keys = []
38
63
  for blob in blobs:
39
64
  # Avoid finding files recursevively in the dir path.
@@ -19,12 +19,27 @@ class LocalStorage(BaseStorage):
19
19
  def isdir(self, path: str) -> bool:
20
20
  return os.path.isdir(path)
21
21
 
22
- def listdir(self, path: str, suffix: str = None) -> List[str]:
22
+ def listdir(
23
+ self,
24
+ path: str,
25
+ suffix: str = None,
26
+ max_results: int = None,
27
+ ) -> List[str]:
28
+ paths = []
23
29
  if not os.path.exists(path):
24
- return []
25
- paths = os.listdir(path)
30
+ return paths
31
+
32
+ if max_results is not None:
33
+ with os.scandir(path) as it:
34
+ for idx, entry in enumerate(it):
35
+ paths.append(entry.name)
36
+ if idx >= max_results - 1:
37
+ break
38
+ else:
39
+ paths = os.listdir(path)
26
40
  if suffix is not None:
27
41
  paths = [p for p in paths if p.endswith(suffix)]
42
+
28
43
  return paths
29
44
 
30
45
  def makedirs(self, path: str, **kwargs) -> None:
@@ -30,11 +30,16 @@ class S3Storage(BaseStorage):
30
30
  path += '/'
31
31
  return self.path_exists(path)
32
32
 
33
- def listdir(self, path: str, suffix: str = None) -> List[str]:
33
+ def listdir(
34
+ self,
35
+ path: str,
36
+ suffix: str = None,
37
+ max_results: int = None,
38
+ ) -> List[str]:
34
39
  if not path.endswith('/'):
35
40
  path += '/'
36
41
  path = s3_url_path(path)
37
- keys = self.client.listdir(path, suffix=suffix)
42
+ keys = self.client.listdir(path, suffix=suffix, max_results=max_results)
38
43
  return [k[len(path):].rstrip('/') for k in keys]
39
44
 
40
45
  def makedirs(self, path: str, **kwargs) -> None:
@@ -0,0 +1,23 @@
1
+ from mage_ai.streaming.sources.base_python import BasePythonSource
2
+ from typing import Callable
3
+
4
+ if 'streaming_source' not in globals():
5
+ from mage_ai.data_preparation.decorators import streaming_source
6
+
7
+
8
+ @streaming_source
9
+ class CustomSource(BasePythonSource):
10
+ def init_client(self):
11
+ """
12
+ Implement the logic of initializing the client.
13
+ """
14
+
15
+ def batch_read(self, handler: Callable):
16
+ """
17
+ Batch read the messages from the source and use handler to process the messages.
18
+ """
19
+ while True:
20
+ records = []
21
+ # Implement the logic of fetching the records
22
+ if len(records) > 0:
23
+ handler(records)
@@ -3,6 +3,12 @@ project_type: main
3
3
  variables_dir: ~/.mage_data
4
4
  # remote_variables_dir: s3://bucket/path_prefix
5
5
 
6
+ # Project metadata that will be prepopulated for workspaces created from this project.
7
+ workspace_initial_metadata: {}
8
+
9
+ # Default workspace configuration.
10
+ workspace_default_config: {}
11
+
6
12
  variables_retention_period: '90d'
7
13
 
8
14
  emr_config:
@@ -140,19 +140,23 @@ def __fetch_data_loader_templates(
140
140
  pipeline_type: PipelineType = PipelineType.PYTHON,
141
141
  ) -> str:
142
142
  data_source = config.get('data_source')
143
+ default_template_name = None
143
144
  file_extension = 'py'
144
145
  if pipeline_type == PipelineType.PYSPARK:
145
146
  template_folder = 'data_loaders/pyspark'
146
147
  elif pipeline_type == PipelineType.STREAMING:
147
148
  template_folder = 'data_loaders/streaming'
148
- file_extension = 'yaml'
149
+ if language == BlockLanguage.YAML:
150
+ file_extension = 'yaml'
151
+ else:
152
+ default_template_name = 'generic_python.py'
149
153
  elif language == BlockLanguage.R:
150
154
  template_folder = 'data_loaders/r'
151
155
  file_extension = 'r'
152
156
  else:
153
157
  template_folder = 'data_loaders'
154
158
 
155
- default_template = template_folder + '/' + 'default.jinja'
159
+ default_template = template_folder + '/' + (default_template_name or 'default.jinja')
156
160
  if data_source is None:
157
161
  template_path = default_template
158
162
  else:
@@ -271,6 +271,7 @@ class VariableManager:
271
271
  block_uuid: str,
272
272
  partition: str = None,
273
273
  clean_block_uuid: bool = True,
274
+ max_results: int = None,
274
275
  ) -> List[str]:
275
276
  variable_dir_path = os.path.join(
276
277
  self.pipeline_path(pipeline_uuid),
@@ -280,7 +281,7 @@ class VariableManager:
280
281
  )
281
282
  if not self.storage.path_exists(variable_dir_path):
282
283
  return []
283
- variables = self.storage.listdir(variable_dir_path)
284
+ variables = self.storage.listdir(variable_dir_path, max_results=max_results)
284
285
  return sorted([v.split('.')[0] for v in variables])
285
286
 
286
287
  def pipeline_path(self, pipeline_uuid: str) -> str:
mage_ai/io/base.py CHANGED
@@ -302,8 +302,11 @@ class BaseSQLDatabase(BaseIO):
302
302
  self,
303
303
  column_name: str,
304
304
  allow_reserved_words: bool = False,
305
+ auto_clean_name: bool = True,
305
306
  case_sensitive: bool = False,
306
307
  ) -> str:
308
+ if not auto_clean_name:
309
+ return False
307
310
  col_new = clean_name(column_name, case_sensitive=case_sensitive)
308
311
  if not allow_reserved_words and col_new.upper() in SQL_RESERVED_WORDS:
309
312
  col_new = f'_{col_new}'
mage_ai/io/bigquery.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from typing import Dict, List, Mapping, Union
2
2
 
3
+ import numpy as np
3
4
  from google.cloud.bigquery import (
4
5
  Client,
5
6
  LoadJobConfig,
@@ -295,6 +296,7 @@ WHERE table_id = '{table_name}'
295
296
  column_types = self.get_column_types(schema, table_name)
296
297
 
297
298
  if df is not None:
299
+ df.fillna(value=np.NaN, inplace=True)
298
300
  for col in df.columns:
299
301
  col_type = column_types.get(col)
300
302
  if not col_type: