mage-ai 0.9.13__py3-none-any.whl → 0.9.15__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 (187) hide show
  1. mage_ai/ai/generator.py +1 -1
  2. mage_ai/ai/generator_cmds.py +1 -1
  3. mage_ai/ai/llm_pipeline_wizard.py +31 -17
  4. mage_ai/api/policies/PipelineRunPolicy.py +1 -0
  5. mage_ai/api/presenters/BlockRunPresenter.py +4 -1
  6. mage_ai/api/presenters/PipelineRunPresenter.py +2 -0
  7. mage_ai/api/resources/BlockRunResource.py +18 -12
  8. mage_ai/api/resources/GitBranchResource.py +8 -1
  9. mage_ai/api/resources/PipelineResource.py +30 -14
  10. mage_ai/api/resources/PipelineRunResource.py +8 -5
  11. mage_ai/data_integrations/sources/constants.py +1 -0
  12. mage_ai/data_preparation/executors/block_executor.py +5 -7
  13. mage_ai/data_preparation/git/__init__.py +18 -0
  14. mage_ai/data_preparation/models/block/__init__.py +13 -1
  15. mage_ai/data_preparation/models/block/dbt/utils/__init__.py +5 -2
  16. mage_ai/data_preparation/models/block/integration/__init__.py +1 -1
  17. mage_ai/data_preparation/models/custom_templates/utils.py +14 -4
  18. mage_ai/data_preparation/models/variable.py +5 -2
  19. mage_ai/data_preparation/templates/constants.py +16 -0
  20. mage_ai/data_preparation/templates/data_exporters/mssql.py +32 -0
  21. mage_ai/data_preparation/templates/data_loaders/mssql.py +27 -0
  22. mage_ai/io/base.py +5 -0
  23. mage_ai/io/mssql.py +1 -0
  24. mage_ai/orchestration/db/models/base.py +15 -15
  25. mage_ai/orchestration/db/models/oauth.py +35 -4
  26. mage_ai/orchestration/db/models/schedules.py +4 -0
  27. mage_ai/orchestration/pipeline_scheduler.py +12 -6
  28. mage_ai/server/constants.py +1 -1
  29. mage_ai/server/frontend_dist/404.html +2 -2
  30. mage_ai/server/frontend_dist/404.html.html +2 -2
  31. mage_ai/server/frontend_dist/_next/static/chunks/{1005-ee665ba499795660.js → 1005-a2f0e3ee378ef02b.js} +1 -1
  32. mage_ai/server/frontend_dist/_next/static/chunks/1424-fca78f21a81a7183.js +1 -0
  33. mage_ai/server/frontend_dist/_next/static/chunks/2786-756b3834d10b8711.js +1 -0
  34. mage_ai/server/frontend_dist/_next/static/chunks/3391-e1578fe1bdb5a557.js +1 -0
  35. mage_ai/server/frontend_dist/_next/static/chunks/6299-9b785c07dacf22b3.js +1 -0
  36. mage_ai/server/frontend_dist/_next/static/chunks/7496-7e4dd11e3f3b8e79.js +1 -0
  37. mage_ai/server/frontend_dist/_next/static/chunks/8952-050e60a8b1eaa32a.js +1 -0
  38. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/9605-e40d076d9fc36d83.js → frontend_dist/_next/static/chunks/9605-9332e1686c46da7d.js} +1 -1
  39. mage_ai/server/frontend_dist/_next/static/chunks/pages/{_app-ccc78f4c29a2a431.js → _app-9dae6fa5126cf001.js} +1 -1
  40. mage_ai/server/frontend_dist/_next/static/chunks/pages/files-03841b6dd7d240f6.js +1 -0
  41. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/settings-6e2c0e3f818fd4de.js +1 -0
  42. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-6e59dc4e57dd2680.js +1 -0
  43. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/logs-02295848c811e26a.js +1 -0
  44. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs-4a2671811a153411.js +1 -0
  45. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{syncs-ddaca2c267def72a.js → syncs-484581ae34a1c596.js} +1 -1
  46. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-ce11db27e4af7869.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-91889109af36a793.js} +1 -1
  47. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-ff1faac7a72b1af1.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-624a2d7cbe6303b4.js} +1 -1
  48. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/triggers-eee16a91f281f92d.js → frontend_dist/_next/static/chunks/pages/triggers-71bbb30dd9b57f80.js} +1 -1
  49. mage_ai/server/{frontend_dist_base_path_template/_next/static/gJQ7p6K0VGDZzX1HXtRED → frontend_dist/_next/static/h5mR3pjfn_EQy348CZ_ok}/_buildManifest.js +1 -1
  50. mage_ai/server/frontend_dist/files.html +2 -2
  51. mage_ai/server/frontend_dist/global-data-products/[...slug].html +2 -2
  52. mage_ai/server/frontend_dist/global-data-products.html +2 -2
  53. mage_ai/server/frontend_dist/index.html +2 -2
  54. mage_ai/server/frontend_dist/manage/settings.html +2 -2
  55. mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
  56. mage_ai/server/frontend_dist/manage/users/new.html +2 -2
  57. mage_ai/server/frontend_dist/manage/users.html +2 -2
  58. mage_ai/server/frontend_dist/manage.html +2 -2
  59. mage_ai/server/frontend_dist/overview.html +2 -2
  60. mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
  61. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  62. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
  63. mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
  64. mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
  65. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  66. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  67. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
  68. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
  69. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
  70. mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
  71. mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
  72. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  73. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
  74. mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
  75. mage_ai/server/frontend_dist/pipelines.html +2 -2
  76. mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
  77. mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
  78. mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
  79. mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
  80. mage_ai/server/frontend_dist/settings.html +2 -2
  81. mage_ai/server/frontend_dist/sign-in.html +11 -11
  82. mage_ai/server/frontend_dist/templates/[...slug].html +2 -2
  83. mage_ai/server/frontend_dist/templates.html +2 -2
  84. mage_ai/server/frontend_dist/terminal.html +2 -2
  85. mage_ai/server/frontend_dist/test.html +3 -3
  86. mage_ai/server/frontend_dist/triggers.html +2 -2
  87. mage_ai/server/frontend_dist/version-control.html +2 -2
  88. mage_ai/server/frontend_dist_base_path_template/404.html +2 -2
  89. mage_ai/server/frontend_dist_base_path_template/404.html.html +2 -2
  90. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{1005-ee665ba499795660.js → 1005-a2f0e3ee378ef02b.js} +1 -1
  91. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1424-fca78f21a81a7183.js +1 -0
  92. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2786-756b3834d10b8711.js +1 -0
  93. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3391-e1578fe1bdb5a557.js +1 -0
  94. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/6299-9b785c07dacf22b3.js +1 -0
  95. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7496-7e4dd11e3f3b8e79.js +1 -0
  96. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/8952-050e60a8b1eaa32a.js +1 -0
  97. mage_ai/server/{frontend_dist/_next/static/chunks/9605-e40d076d9fc36d83.js → frontend_dist_base_path_template/_next/static/chunks/9605-9332e1686c46da7d.js} +1 -1
  98. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{_app-ccc78f4c29a2a431.js → _app-9dae6fa5126cf001.js} +1 -1
  99. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/files-03841b6dd7d240f6.js +1 -0
  100. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/settings-6e2c0e3f818fd4de.js +1 -0
  101. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-6e59dc4e57dd2680.js +1 -0
  102. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/logs-02295848c811e26a.js +1 -0
  103. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs-4a2671811a153411.js +1 -0
  104. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{syncs-ddaca2c267def72a.js → syncs-484581ae34a1c596.js} +1 -1
  105. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-ce11db27e4af7869.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-91889109af36a793.js} +1 -1
  106. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-ff1faac7a72b1af1.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-624a2d7cbe6303b4.js} +1 -1
  107. mage_ai/server/{frontend_dist/_next/static/chunks/pages/triggers-eee16a91f281f92d.js → frontend_dist_base_path_template/_next/static/chunks/pages/triggers-71bbb30dd9b57f80.js} +1 -1
  108. mage_ai/server/{frontend_dist/_next/static/O4HzlRY2U3Q47jF8s3D-E → frontend_dist_base_path_template/_next/static/xykYLW_ARw5SevdiponLI}/_buildManifest.js +1 -1
  109. mage_ai/server/frontend_dist_base_path_template/files.html +2 -2
  110. mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +2 -2
  111. mage_ai/server/frontend_dist_base_path_template/global-data-products.html +2 -2
  112. mage_ai/server/frontend_dist_base_path_template/index.html +2 -2
  113. mage_ai/server/frontend_dist_base_path_template/manage/settings.html +2 -2
  114. mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +2 -2
  115. mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +2 -2
  116. mage_ai/server/frontend_dist_base_path_template/manage/users.html +2 -2
  117. mage_ai/server/frontend_dist_base_path_template/manage.html +2 -2
  118. mage_ai/server/frontend_dist_base_path_template/overview.html +2 -2
  119. mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +2 -2
  120. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  121. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +2 -2
  122. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +2 -2
  123. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +2 -2
  124. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  125. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  126. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +2 -2
  127. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +2 -2
  128. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +2 -2
  129. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +2 -2
  130. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +2 -2
  131. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  132. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +2 -2
  133. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +2 -2
  134. mage_ai/server/frontend_dist_base_path_template/pipelines.html +2 -2
  135. mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +2 -2
  136. mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +2 -2
  137. mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +2 -2
  138. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +2 -2
  139. mage_ai/server/frontend_dist_base_path_template/settings.html +2 -2
  140. mage_ai/server/frontend_dist_base_path_template/sign-in.html +11 -11
  141. mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +2 -2
  142. mage_ai/server/frontend_dist_base_path_template/templates.html +2 -2
  143. mage_ai/server/frontend_dist_base_path_template/terminal.html +2 -2
  144. mage_ai/server/frontend_dist_base_path_template/test.html +3 -3
  145. mage_ai/server/frontend_dist_base_path_template/triggers.html +2 -2
  146. mage_ai/server/frontend_dist_base_path_template/version-control.html +2 -2
  147. mage_ai/server/server.py +12 -1
  148. mage_ai/services/spark/spark.py +75 -2
  149. mage_ai/shared/retry.py +1 -1
  150. mage_ai/tests/data_preparation/models/custom_templates/__init__.py +0 -0
  151. mage_ai/tests/data_preparation/models/custom_templates/test_utils.py +36 -0
  152. mage_ai/tests/data_preparation/models/test_block.py +48 -0
  153. mage_ai/tests/services/spark/__init__.py +0 -0
  154. mage_ai/tests/services/spark/test_spark.py +52 -0
  155. {mage_ai-0.9.13.dist-info → mage_ai-0.9.15.dist-info}/METADATA +1 -1
  156. {mage_ai-0.9.13.dist-info → mage_ai-0.9.15.dist-info}/RECORD +164 -159
  157. mage_ai/server/frontend_dist/_next/static/chunks/1424-846b754e84e6eac1.js +0 -1
  158. mage_ai/server/frontend_dist/_next/static/chunks/2786-ba6f486fcaf52ba0.js +0 -1
  159. mage_ai/server/frontend_dist/_next/static/chunks/3391-6f0a0a5c254cd7f2.js +0 -1
  160. mage_ai/server/frontend_dist/_next/static/chunks/6299-fcb702d0f3d3291b.js +0 -1
  161. mage_ai/server/frontend_dist/_next/static/chunks/7496-7d0f9adf0b333801.js +0 -1
  162. mage_ai/server/frontend_dist/_next/static/chunks/8952-9d6fa18fa9378989.js +0 -1
  163. mage_ai/server/frontend_dist/_next/static/chunks/pages/files-2dc2a0dfc0ff620d.js +0 -1
  164. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/settings-6577159a0af52a78.js +0 -1
  165. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-615ab9e61f0f39ed.js +0 -1
  166. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/logs-d331e98ad103b13e.js +0 -1
  167. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs-df014ddb14ebcef4.js +0 -1
  168. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1424-846b754e84e6eac1.js +0 -1
  169. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2786-ba6f486fcaf52ba0.js +0 -1
  170. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3391-6f0a0a5c254cd7f2.js +0 -1
  171. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/6299-fcb702d0f3d3291b.js +0 -1
  172. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7496-7d0f9adf0b333801.js +0 -1
  173. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/8952-9d6fa18fa9378989.js +0 -1
  174. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/files-2dc2a0dfc0ff620d.js +0 -1
  175. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/settings-6577159a0af52a78.js +0 -1
  176. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-615ab9e61f0f39ed.js +0 -1
  177. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/logs-d331e98ad103b13e.js +0 -1
  178. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs-df014ddb14ebcef4.js +0 -1
  179. mage_ai/tests/orchestration/db/test_models.py +0 -710
  180. /mage_ai/server/frontend_dist/_next/static/{O4HzlRY2U3Q47jF8s3D-E → h5mR3pjfn_EQy348CZ_ok}/_middlewareManifest.js +0 -0
  181. /mage_ai/server/frontend_dist/_next/static/{O4HzlRY2U3Q47jF8s3D-E → h5mR3pjfn_EQy348CZ_ok}/_ssgManifest.js +0 -0
  182. /mage_ai/server/frontend_dist_base_path_template/_next/static/{gJQ7p6K0VGDZzX1HXtRED → xykYLW_ARw5SevdiponLI}/_middlewareManifest.js +0 -0
  183. /mage_ai/server/frontend_dist_base_path_template/_next/static/{gJQ7p6K0VGDZzX1HXtRED → xykYLW_ARw5SevdiponLI}/_ssgManifest.js +0 -0
  184. {mage_ai-0.9.13.dist-info → mage_ai-0.9.15.dist-info}/LICENSE +0 -0
  185. {mage_ai-0.9.13.dist-info → mage_ai-0.9.15.dist-info}/WHEEL +0 -0
  186. {mage_ai-0.9.13.dist-info → mage_ai-0.9.15.dist-info}/entry_points.txt +0 -0
  187. {mage_ai-0.9.13.dist-info → mage_ai-0.9.15.dist-info}/top_level.txt +0 -0
mage_ai/ai/generator.py CHANGED
@@ -33,7 +33,7 @@ class Generator:
33
33
  elif use_case == LLMUseCase.GENERATE_PIPELINE_WITH_DESCRIPTION:
34
34
  from mage_ai.ai.llm_pipeline_wizard import LLMPipelineWizard
35
35
 
36
- return await LLMPipelineWizard().async_generate_pipeline_with_description(
36
+ return await LLMPipelineWizard().async_generate_pipeline_from_description(
37
37
  request.get('pipeline_description'),
38
38
  )
39
39
  elif use_case == LLMUseCase.GENERATE_COMMENT_FOR_CODE:
@@ -49,7 +49,7 @@ def generate_block_with_description(block_description: str):
49
49
 
50
50
  @app.command()
51
51
  def generate_pipeline_with_description(pipeline_description: str):
52
- print(asyncio.run(LLMPipelineWizard().async_generate_pipeline_with_description(
52
+ print(asyncio.run(LLMPipelineWizard().async_generate_pipeline_from_description(
53
53
  pipeline_description)))
54
54
 
55
55
 
@@ -2,6 +2,7 @@ import ast
2
2
  import asyncio
3
3
  import json
4
4
  import os
5
+ import re
5
6
  from typing import Dict
6
7
 
7
8
  import openai
@@ -53,23 +54,24 @@ PROMPT_TO_SPLIT_BLOCKS = """
53
54
  A BLOCK does one action either reading data from one data source, transforming the data from
54
55
  one format to another or exporting data into a data source.
55
56
  Based on the code description delimited by triple backticks, your task is to identify
56
- how many BLOCKS required and function for each BLOCK.
57
+ how many BLOCKS required, function for each BLOCK and upstream blocks between BLOCKs.
57
58
 
58
59
  Use the following format:
59
- BLOCK 1: <block function>
60
- BLOCK 2: <block function>
61
- BLOCK 3: <block function>
60
+ BLOCK 1: function: <block function>. upstream: <upstream blocks>
61
+ BLOCK 2: function: <block function>. upstream: <upstream blocks>
62
+ BLOCK 3: function: <block function>. upstream: <upstream blocks>
62
63
  ...
63
64
 
64
65
  Example:
65
66
  <code description>: ```
66
- Read data from MySQL, filter out rows with book_price > 100, and save data to BigQuery.
67
+ Read data from MySQL and Postgres, filter out rows with book_price > 100, and save data to BigQuery.
67
68
  ```
68
69
 
69
70
  Answer:
70
- BLOCK 1: load data from MySQL
71
- BLOCK 2: filter out rows with book_price > 100
72
- BLOCK 3: export data to BigQuery
71
+ BLOCK 1: function: load data from MySQL. upstream:
72
+ BLOCK 2: function: load data from Postgres. upstream:
73
+ BLOCK 3: function: filter out rows with book_price > 100. upstream: 1, 2
74
+ BLOCK 4: function: export data to BigQuery. upstream: 3
73
75
 
74
76
  <code description>: ```{code_description}```"""
75
77
  PROMPT_FOR_FUNCTION_COMMENT = """
@@ -81,6 +83,7 @@ Your task is to write comments for each function inside.
81
83
  The comment should follow Google Docstring format.
82
84
  Return your response in JSON format with function name as key and the comment as value.
83
85
  """
86
+ BLOCK_SPLIT_PATTERN = r"BLOCK\s+(\w+):\s+function:\s+(.*?)\.\s+upstream:\s*(.*?)$"
84
87
  TRANSFORMERS_FOLDER = 'transformers'
85
88
  CLASSIFICATION_FUNCTION_NAME = "classify_description"
86
89
  TEMPLATE_CLASSIFICATION_FUNCTION = [
@@ -197,7 +200,10 @@ class LLMPipelineWizard:
197
200
  function_args.get(DataSource.__name__))
198
201
  return block_type, block_language, pipeline_type, config
199
202
 
200
- async def async_generate_block_with_description(self, block_description: str) -> dict:
203
+ async def async_generate_block_with_description(
204
+ self,
205
+ block_description: str,
206
+ upstream_blocks: [str]) -> dict:
201
207
  messages = [{"role": "user", "content": block_description}]
202
208
  response = await openai.ChatCompletion.acreate(
203
209
  model="gpt-3.5-turbo-0613",
@@ -220,6 +226,7 @@ class LLMPipelineWizard:
220
226
  pipeline_type=pipeline_type,
221
227
  ),
222
228
  language=block_language,
229
+ upstream_blocks=upstream_blocks,
223
230
  )
224
231
  else:
225
232
  logger.error("Failed to interpret the description as a block template.")
@@ -238,11 +245,12 @@ class LLMPipelineWizard:
238
245
  async def __async_generate_blocks(self,
239
246
  block_dict: dict,
240
247
  block_id: int,
241
- block_description: str) -> dict:
242
- block = await self.async_generate_block_with_description(block_description)
248
+ block_description: str,
249
+ upstream_blocks: [str]) -> dict:
250
+ block = await self.async_generate_block_with_description(block_description, upstream_blocks)
243
251
  block_dict[block_id] = block
244
252
 
245
- async def async_generate_pipeline_with_description(self, pipeline_description: str) -> dict:
253
+ async def async_generate_pipeline_from_description(self, pipeline_description: str) -> dict:
246
254
  splited_block_descriptions = await self.__async_split_description_by_blocks(
247
255
  pipeline_description)
248
256
  blocks = {}
@@ -250,11 +258,17 @@ class LLMPipelineWizard:
250
258
  for line in splited_block_descriptions.strip().split('\n'):
251
259
  if line.startswith("BLOCK") and ":" in line:
252
260
  # Extract the block_id and block_description from the line
253
- raw_block_id, block_description = line.split(":", 1)
254
- block_id = raw_block_id.strip().split(" ")[-1]
255
- block_description = block_description.strip()
256
- block_tasks.append(
257
- self.__async_generate_blocks(blocks, block_id, block_description))
261
+ match = re.search(BLOCK_SPLIT_PATTERN, line)
262
+ if match:
263
+ block_id = match.group(1)
264
+ block_description = match.group(2).strip()
265
+ upstream_blocks = match.group(3).split(", ")
266
+ block_tasks.append(
267
+ self.__async_generate_blocks(
268
+ blocks,
269
+ block_id,
270
+ block_description,
271
+ upstream_blocks))
258
272
  await asyncio.gather(*block_tasks)
259
273
  return blocks
260
274
 
@@ -47,6 +47,7 @@ PipelineRunPolicy.allow_read(PipelineRunPresenter.default_attributes + [
47
47
  PipelineRunPolicy.allow_read(PipelineRunPresenter.default_attributes + [
48
48
  'block_runs',
49
49
  'block_runs_count',
50
+ 'completed_block_runs_count',
50
51
  'pipeline_schedule_name',
51
52
  'pipeline_schedule_token',
52
53
  'pipeline_schedule_type',
@@ -17,4 +17,7 @@ class BlockRunPresenter(BasePresenter):
17
17
  ]
18
18
 
19
19
  def present(self, **kwargs):
20
- return self.model.to_dict()
20
+ if isinstance(self.model, dict):
21
+ return self.model
22
+ else:
23
+ return self.model.to_dict()
@@ -27,6 +27,7 @@ class PipelineRunPresenter(BasePresenter):
27
27
  additional_attributes = [
28
28
  'block_runs',
29
29
  'block_runs_count',
30
+ 'completed_block_runs_count',
30
31
  'pipeline_schedule_name',
31
32
  'pipeline_schedule_token',
32
33
  'pipeline_schedule_type',
@@ -66,6 +67,7 @@ PipelineRunPresenter.register_format(
66
67
  PipelineRunPresenter.default_attributes + [
67
68
  'block_runs',
68
69
  'block_runs_count',
70
+ 'completed_block_runs_count',
69
71
  'pipeline_schedule_name',
70
72
  'pipeline_schedule_token',
71
73
  'pipeline_schedule_type',
@@ -1,7 +1,12 @@
1
+ from sqlalchemy.orm import aliased
2
+
1
3
  from mage_ai.api.resources.DatabaseResource import DatabaseResource
2
- from mage_ai.orchestration.db.models.schedules import BlockRun, PipelineRun, PipelineSchedule
3
4
  from mage_ai.orchestration.db import safe_db_query
4
- from sqlalchemy.orm import aliased
5
+ from mage_ai.orchestration.db.models.schedules import (
6
+ BlockRun,
7
+ PipelineRun,
8
+ PipelineSchedule,
9
+ )
5
10
 
6
11
 
7
12
  class BlockRunResource(DatabaseResource):
@@ -24,16 +29,17 @@ class BlockRunResource(DatabaseResource):
24
29
  pipeline_schedule_name,
25
30
  ) = tup
26
31
 
27
- block_run = BlockRun()
28
- block_run.block_uuid = block_uuid
29
- block_run.completed_at = completed_at
30
- block_run.created_at = created_at
31
- block_run.id = id
32
- block_run.pipeline_run_id = pipeline_run_id
33
- block_run.status = status
34
- block_run.updated_at = updated_at
35
- block_run.pipeline_schedule_id = pipeline_schedule_id
36
- block_run.pipeline_schedule_name = pipeline_schedule_name
32
+ block_run = dict(
33
+ block_uuid=block_uuid,
34
+ completed_at=completed_at,
35
+ created_at=created_at,
36
+ id=id,
37
+ pipeline_run_id=pipeline_run_id,
38
+ status=status,
39
+ updated_at=updated_at,
40
+ pipeline_schedule_id=pipeline_schedule_id,
41
+ pipeline_schedule_name=pipeline_schedule_name,
42
+ )
37
43
 
38
44
  block_runs.append(block_run)
39
45
 
@@ -265,7 +265,14 @@ class GitBranchResource(GenericResource):
265
265
  untracked_files: List[str],
266
266
  limit: int = None
267
267
  ) -> Dict:
268
- arr = modified_files + staged_files + untracked_files
268
+ arr = []
269
+
270
+ if modified_files and isinstance(modified_files, list):
271
+ arr += modified_files
272
+ if staged_files and isinstance(staged_files, list):
273
+ arr += staged_files
274
+ if untracked_files and isinstance(untracked_files, list):
275
+ arr += untracked_files
269
276
 
270
277
  if limit:
271
278
  arr = arr[:limit]
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ from typing import Dict, List
2
3
 
3
4
  from sqlalchemy import or_
4
5
  from sqlalchemy.orm import aliased
@@ -312,17 +313,28 @@ class PipelineResource(BaseResource):
312
313
  schedule.update(status=status)
313
314
 
314
315
  @safe_db_query
315
- def cancel_pipeline_runs(status, pipeline_uuid):
316
- pipeline_runs = (
317
- PipelineRun.
318
- query.
319
- filter(PipelineRun.pipeline_uuid == pipeline_uuid).
320
- filter(PipelineRun.status.in_([
321
- PipelineRun.PipelineRunStatus.INITIAL,
322
- PipelineRun.PipelineRunStatus.RUNNING,
323
- ]))
324
- )
325
- for pipeline_run in pipeline_runs:
316
+ def cancel_pipeline_runs(
317
+ pipeline_uuid: str = None,
318
+ pipeline_runs: List[Dict] = None,
319
+ ):
320
+ if pipeline_runs is not None:
321
+ pipeline_run_ids = [run.get('id') for run in pipeline_runs]
322
+ pipeline_runs_to_cancel = (
323
+ PipelineRun.
324
+ query.
325
+ filter(PipelineRun.id.in_(pipeline_run_ids))
326
+ )
327
+ else:
328
+ pipeline_runs_to_cancel = (
329
+ PipelineRun.
330
+ query.
331
+ filter(PipelineRun.pipeline_uuid == pipeline_uuid).
332
+ filter(PipelineRun.status.in_([
333
+ PipelineRun.PipelineRunStatus.INITIAL,
334
+ PipelineRun.PipelineRunStatus.RUNNING,
335
+ ]))
336
+ )
337
+ for pipeline_run in pipeline_runs_to_cancel:
326
338
  PipelineScheduler(pipeline_run).stop()
327
339
 
328
340
  def retry_pipeline_runs(pipeline_runs):
@@ -334,15 +346,19 @@ class PipelineResource(BaseResource):
334
346
 
335
347
  def _update_callback(resource):
336
348
  if status:
349
+ pipeline_runs = payload.get('pipeline_runs')
337
350
  if status in [
338
351
  ScheduleStatus.ACTIVE.value,
339
352
  ScheduleStatus.INACTIVE.value,
340
353
  ]:
341
354
  update_schedule_status(status, pipeline_uuid)
342
355
  elif status == PipelineRun.PipelineRunStatus.CANCELLED.value:
343
- cancel_pipeline_runs(status, pipeline_uuid)
344
- elif status == 'retry' and payload.get('pipeline_runs'):
345
- retry_pipeline_runs(payload.get('pipeline_runs'))
356
+ if pipeline_runs is None:
357
+ cancel_pipeline_runs(pipeline_uuid=pipeline_uuid)
358
+ else:
359
+ cancel_pipeline_runs(pipeline_runs=pipeline_runs)
360
+ elif status == 'retry' and pipeline_runs:
361
+ retry_pipeline_runs(pipeline_runs)
346
362
 
347
363
  self.on_update_callback = _update_callback
348
364
 
@@ -60,12 +60,15 @@ class PipelineRunResource(DatabaseResource):
60
60
  else:
61
61
  order_by.append((parts[0], 'asc'))
62
62
 
63
+ repo_pipeline_schedule_ids = [s.id for s in PipelineSchedule.repo_query]
64
+
63
65
  results = (
64
- PipelineRun.
65
- query.
66
- options(selectinload(PipelineRun.block_runs)).
67
- options(selectinload(PipelineRun.pipeline_schedule)).
68
- join(PipelineSchedule, PipelineRun.pipeline_schedule_id == PipelineSchedule.id)
66
+ PipelineRun
67
+ .query
68
+ .filter(PipelineRun.pipeline_schedule_id.in_(repo_pipeline_schedule_ids))
69
+ .options(selectinload(PipelineRun.block_runs))
70
+ .options(selectinload(PipelineRun.pipeline_schedule))
71
+ .join(PipelineSchedule, PipelineRun.pipeline_schedule_id == PipelineSchedule.id)
69
72
  )
70
73
 
71
74
  if global_data_product_uuid is not None:
@@ -38,6 +38,7 @@ SOURCES = sorted([
38
38
  dict(name='Pipedrive'),
39
39
  dict(name='Postmark'),
40
40
  dict(name='Salesforce'),
41
+ dict(name='Sftp'),
41
42
  dict(name='Stripe'),
42
43
  dict(name='Twitter Ads'),
43
44
  dict(name='Zendesk'),
@@ -252,20 +252,18 @@ class BlockExecutor:
252
252
  )
253
253
 
254
254
  result = __execute_with_retry()
255
- except Exception as e:
255
+ except Exception as error:
256
256
  self.logger.exception(
257
257
  f'Failed to execute block {self.block.uuid}',
258
258
  **merge_dict(tags, dict(
259
- block_type=self.block.type,
260
- block_uuid=self.block.uuid,
261
- error=e
259
+ error=error,
262
260
  )),
263
261
  )
264
262
  if on_failure is not None:
265
263
  on_failure(
266
264
  self.block_uuid,
267
265
  error=dict(
268
- error=str(e),
266
+ error=error,
269
267
  errors=traceback.format_stack(),
270
268
  message=traceback.format_exc(),
271
269
  ),
@@ -285,7 +283,7 @@ class BlockExecutor:
285
283
  logging_tags=tags,
286
284
  pipeline_run=pipeline_run,
287
285
  )
288
- raise e
286
+ raise error
289
287
  self.logger.info(f'Finish executing block with {self.__class__.__name__}.', **tags)
290
288
  if on_complete is not None:
291
289
  on_complete(self.block_uuid)
@@ -537,7 +535,7 @@ class BlockExecutor:
537
535
  tags: Dict = None,
538
536
  ):
539
537
  """
540
- Update the status of block run by edither updating the BlockRun db object or making
538
+ Update the status of block run by either updating the BlockRun db object or making
541
539
  API call
542
540
 
543
541
  Args:
@@ -434,6 +434,24 @@ class Git:
434
434
  for url in remote.urls:
435
435
  if url.lower().startswith('https'):
436
436
  repository_names.append('/'.join(url.split('/')[-2:]).replace('.git', ''))
437
+
438
+ # Remove the token from the URL
439
+ # e.g. https://[user]:[token]@[netloc]
440
+ parts = url.split('@')
441
+
442
+ parts_arr = []
443
+ if len(parts) >= 2:
444
+ # https://[user]:[token]
445
+ parts2 = parts[0].split(':')
446
+ # ['https', '', 'user', 'token']
447
+ parts2[len(parts2) - 1] = '[token]'
448
+ parts_arr.append(':'.join(parts2))
449
+ parts_arr += parts[1:]
450
+ else:
451
+ parts_arr += parts
452
+
453
+ url = '@'.join(parts_arr)
454
+
437
455
  urls.append(url)
438
456
  except GitCommandError as err:
439
457
  print('WARNING (mage_ai.data_preparation.git.remotes):')
@@ -17,6 +17,7 @@ import pandas as pd
17
17
  import simplejson
18
18
  from jinja2 import Template
19
19
 
20
+ import mage_ai.data_preparation.decorators
20
21
  from mage_ai.cache.block import BlockCache
21
22
  from mage_ai.data_cleaner.shared.utils import is_geo_dataframe, is_spark_dataframe
22
23
  from mage_ai.data_preparation.logging.logger import DictLogger
@@ -1110,6 +1111,8 @@ class Block:
1110
1111
  ) -> List:
1111
1112
  if logging_tags is None:
1112
1113
  logging_tags = dict()
1114
+ if input_vars is None:
1115
+ input_vars = list()
1113
1116
 
1114
1117
  decorated_functions = []
1115
1118
  test_functions = []
@@ -1177,9 +1180,18 @@ class Block:
1177
1180
  block_uuid = self.replicated_block
1178
1181
  block_file_path = self.replicated_block_object.file_path
1179
1182
  spec = importlib.util.spec_from_file_location(
1180
- block_uuid, block_file_path,
1183
+ block_uuid,
1184
+ block_file_path,
1181
1185
  )
1182
1186
  module = importlib.util.module_from_spec(spec)
1187
+ # Set the decorators in the module in case they are not defined in the block
1188
+ # code
1189
+ setattr(
1190
+ module,
1191
+ self.type,
1192
+ getattr(mage_ai.data_preparation.decorators, self.type),
1193
+ )
1194
+ module.test = mage_ai.data_preparation.decorators.test
1183
1195
  spec.loader.exec_module(module)
1184
1196
  block_function_updated = getattr(module, block_function.__name__)
1185
1197
  self.module = module
@@ -501,9 +501,12 @@ def config_file_loader_and_configuration(
501
501
  database = kwargs.get('database') or profile.get('project')
502
502
  schema = profile.get('dataset')
503
503
 
504
- config_file_loader = ConfigFileLoader(config=dict(
504
+ config_file_loader_kwargs = dict(
505
505
  GOOGLE_SERVICE_ACC_KEY_FILEPATH=keyfile,
506
- ))
506
+ )
507
+ if profile.get('location'):
508
+ config_file_loader_kwargs['GOOGLE_LOCATION'] = profile.get('location')
509
+ config_file_loader = ConfigFileLoader(config=config_file_loader_kwargs)
507
510
  configuration = dict(
508
511
  data_provider=profile_type,
509
512
  data_provider_database=database,
@@ -122,7 +122,7 @@ class IntegrationBlock(Block):
122
122
  proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
123
123
 
124
124
  for line in proc.stdout:
125
- f.write(filter_out_config_values(line.decode(), config)),
125
+ f.write(line.decode()),
126
126
  print_log_from_line(
127
127
  line,
128
128
  config=config,
@@ -56,13 +56,23 @@ def group_and_hydrate_files(
56
56
  file_dicts: List[Dict],
57
57
  custom_template_class,
58
58
  ) -> List:
59
- groups = group_by(lambda x: os.path.join(*x.get('parent_names', [])), file_dicts)
59
+ def _func(x):
60
+ arr = ['']
60
61
 
61
- arr = []
62
+ if x:
63
+ parent_names = x.get('parent_names', []) or []
64
+ if parent_names and len(parent_names) >= 1:
65
+ arr = [str(parent_name) for parent_name in parent_names]
66
+
67
+ return os.path.join(*arr)
68
+
69
+ groups = group_by(_func, file_dicts)
70
+
71
+ custom_templates = []
62
72
 
63
73
  for template_uuid, _ in groups.items():
64
74
  custom_template = custom_template_class.load(template_uuid=template_uuid)
65
75
  if custom_template:
66
- arr.append(custom_template)
76
+ custom_templates.append(custom_template)
67
77
 
68
- return arr
78
+ return custom_templates
@@ -430,11 +430,14 @@ class Variable:
430
430
  df_output = data.copy()
431
431
  # Clean up data types since parquet doesn't support mixed data types
432
432
  for c in df_output.columns:
433
- c_dtype = df_output[c].dtype
433
+ df_col = df_output[c]
434
+ if type(df_col) is pd.DataFrame:
435
+ raise Exception(f'Please do not use duplicate column name: "{c}"')
436
+ c_dtype = df_col.dtype
434
437
  if not is_object_dtype(c_dtype):
435
438
  column_types[c] = str(c_dtype)
436
439
  else:
437
- series_non_null = df_output[c].dropna()
440
+ series_non_null = df_col.dropna()
438
441
  if len(series_non_null) > 0:
439
442
  coltype = type(series_non_null.iloc[0])
440
443
  if is_object_dtype(series_non_null.dtype):
@@ -51,6 +51,14 @@ TEMPLATES = [
51
51
  name='MongoDB',
52
52
  path='data_loaders/mongodb.py',
53
53
  ),
54
+ dict(
55
+ block_type=BlockType.DATA_LOADER,
56
+ description='Load data from MSSQL.',
57
+ groups=[GROUP_DATABASES],
58
+ language=BlockLanguage.PYTHON,
59
+ name='MSSQL',
60
+ path='data_loaders/mssql.py',
61
+ ),
54
62
  dict(
55
63
  block_type=BlockType.DATA_EXPORTER,
56
64
  description='Export data to a Delta Table in Amazon S3.',
@@ -82,6 +90,14 @@ TEMPLATES = [
82
90
  name='MongoDB',
83
91
  path='data_exporters/mongodb.py',
84
92
  ),
93
+ dict(
94
+ block_type=BlockType.DATA_EXPORTER,
95
+ description='Export data to MSSQL.',
96
+ groups=[GROUP_DATABASES],
97
+ language=BlockLanguage.PYTHON,
98
+ name='MSSQL',
99
+ path='data_exporters/mssql.py',
100
+ ),
85
101
  dict(
86
102
  block_type=BlockType.DATA_LOADER,
87
103
  description='Trigger another pipeline to run.',
@@ -0,0 +1,32 @@
1
+ from mage_ai.settings.repo import get_repo_path
2
+ from mage_ai.io.config import ConfigFileLoader
3
+ from mage_ai.io.mssql import MSSQL
4
+ from pandas import DataFrame
5
+ from os import path
6
+
7
+ if 'data_exporter' not in globals():
8
+ from mage_ai.data_preparation.decorators import data_exporter
9
+
10
+
11
+ @data_exporter
12
+ def export_data_to_mssql(df: DataFrame, **kwargs) -> None:
13
+ """
14
+ Template for exporting data to a MSSQL database.
15
+ Specify your configuration settings in 'io_config.yaml'.
16
+ Set the following in your io_config:
17
+
18
+ Docs: https://docs.mage.ai/integrations/databases/MicrosoftSQLServer
19
+ """
20
+ schema_name = 'dbo' # Specify the name of the schema to export data to
21
+ table_name = 'your_table_name' # Specify the name of the table to export data to
22
+ config_path = path.join(get_repo_path(), 'io_config.yaml')
23
+ config_profile = 'default'
24
+
25
+ with MSSQL.with_config(ConfigFileLoader(config_path, config_profile)) as loader:
26
+ loader.export(
27
+ df,
28
+ schema_name,
29
+ table_name,
30
+ index=False, # Specifies whether to include index in exported table
31
+ if_exists='replace', # Specify resolution policy if table name already exists
32
+ )
@@ -0,0 +1,27 @@
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.mssql import MSSQL
6
+ from os import path
7
+ {{ super() -}}
8
+ {% endblock %}
9
+
10
+
11
+ {% block content %}
12
+ @data_loader
13
+ def load_data_from_mssql(*args, **kwargs):
14
+ """
15
+ Template for loading data from a MSSQL database.
16
+ Specify your configuration settings in 'io_config.yaml'.
17
+ Set the following in your io_config:
18
+
19
+ Docs: https://docs.mage.ai/integrations/databases/MicrosoftSQLServer
20
+ """
21
+ query = 'Your MSSQL query' # Specify your SQL query here
22
+ config_path = path.join(get_repo_path(), 'io_config.yaml')
23
+ config_profile = 'default'
24
+
25
+ with MSSQL.with_config(ConfigFileLoader(config_path, config_profile)) as loader:
26
+ return loader.load(query)
27
+ {% endblock %}
mage_ai/io/base.py CHANGED
@@ -35,6 +35,7 @@ class FileFormat(str, Enum):
35
35
  JSON = 'json'
36
36
  PARQUET = 'parquet'
37
37
  HDF5 = 'hdf5'
38
+ XML = 'xml'
38
39
 
39
40
 
40
41
  class ExportWritePolicy(str, Enum):
@@ -137,6 +138,8 @@ class BaseFile(BaseIO):
137
138
  return pd.read_parquet
138
139
  elif format == FileFormat.HDF5:
139
140
  return pd.read_hdf
141
+ elif format == FileFormat.XML:
142
+ return pd.read_xml
140
143
  else:
141
144
  raise ValueError(f'Invalid format \'{format}\' specified.')
142
145
 
@@ -226,6 +229,8 @@ class BaseFile(BaseIO):
226
229
  return df.to_json
227
230
  elif format == FileFormat.HDF5:
228
231
  return df.to_hdf
232
+ elif format == FileFormat.XML:
233
+ return df.to_xml
229
234
  return df.to_parquet
230
235
 
231
236
  def __del__(self):
mage_ai/io/mssql.py CHANGED
@@ -142,6 +142,7 @@ class MSSQL(BaseSQL):
142
142
  # Remove extraneous surrounding double quotes
143
143
  # that get added while performing conversion to string.
144
144
  df_[col] = df_[col].apply(lambda x: x.strip('"') if x and isinstance(x, str) else x)
145
+ df_.replace({np.NaN: None}, inplace=True)
145
146
  for _, row in df_.iterrows():
146
147
  values.append(tuple(row))
147
148