mage-ai 0.9.70__py3-none-any.whl → 0.9.71__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 (568) hide show
  1. mage_ai/ai/utils/xgboost.py +222 -0
  2. mage_ai/api/errors.py +37 -25
  3. mage_ai/api/operations/base.py +13 -1
  4. mage_ai/api/parsers/PipelineScheduleParser.py +1 -1
  5. mage_ai/api/policies/BlockOutputPolicy.py +40 -17
  6. mage_ai/api/policies/GlobalDataProductPolicy.py +91 -41
  7. mage_ai/api/policies/KernelPolicy.py +55 -32
  8. mage_ai/api/policies/KernelProcessPolicy.py +56 -0
  9. mage_ai/api/policies/OutputPolicy.py +73 -41
  10. mage_ai/api/policies/PipelinePolicy.py +206 -138
  11. mage_ai/api/presenters/BlockLayoutItemPresenter.py +9 -7
  12. mage_ai/api/presenters/BlockPresenter.py +1 -1
  13. mage_ai/api/presenters/GlobalDataProductPresenter.py +6 -1
  14. mage_ai/api/presenters/KernelPresenter.py +5 -26
  15. mage_ai/api/presenters/KernelProcessPresenter.py +28 -0
  16. mage_ai/api/presenters/PipelinePresenter.py +18 -5
  17. mage_ai/api/presenters/StatusPresenter.py +2 -0
  18. mage_ai/api/presenters/SyncPresenter.py +25 -0
  19. mage_ai/api/resources/AutocompleteItemResource.py +1 -1
  20. mage_ai/api/resources/BlockLayoutItemResource.py +90 -44
  21. mage_ai/api/resources/BlockOutputResource.py +42 -9
  22. mage_ai/api/resources/BlockResource.py +4 -3
  23. mage_ai/api/resources/BlockRunResource.py +27 -22
  24. mage_ai/api/resources/ClusterResource.py +4 -1
  25. mage_ai/api/resources/CustomTemplateResource.py +34 -14
  26. mage_ai/api/resources/DataProviderResource.py +1 -1
  27. mage_ai/api/resources/ExecutionStateResource.py +3 -1
  28. mage_ai/api/resources/FileContentResource.py +8 -2
  29. mage_ai/api/resources/FileResource.py +10 -4
  30. mage_ai/api/resources/FileVersionResource.py +3 -1
  31. mage_ai/api/resources/GitBranchResource.py +46 -9
  32. mage_ai/api/resources/GlobalDataProductResource.py +44 -7
  33. mage_ai/api/resources/GlobalHookResource.py +4 -1
  34. mage_ai/api/resources/IntegrationDestinationResource.py +6 -2
  35. mage_ai/api/resources/IntegrationSourceResource.py +8 -4
  36. mage_ai/api/resources/IntegrationSourceStreamResource.py +6 -2
  37. mage_ai/api/resources/KernelProcessResource.py +44 -0
  38. mage_ai/api/resources/KernelResource.py +25 -3
  39. mage_ai/api/resources/OutputResource.py +33 -11
  40. mage_ai/api/resources/PageBlockLayoutResource.py +34 -23
  41. mage_ai/api/resources/PipelineInteractionResource.py +31 -15
  42. mage_ai/api/resources/PipelineResource.py +250 -123
  43. mage_ai/api/resources/PipelineRunResource.py +11 -3
  44. mage_ai/api/resources/PipelineScheduleResource.py +7 -2
  45. mage_ai/api/resources/PipelineTriggerResource.py +6 -1
  46. mage_ai/api/resources/ProjectResource.py +18 -7
  47. mage_ai/api/resources/SecretResource.py +1 -1
  48. mage_ai/api/resources/SeedResource.py +8 -1
  49. mage_ai/api/resources/StatusResource.py +21 -6
  50. mage_ai/api/resources/SyncResource.py +6 -8
  51. mage_ai/api/resources/VariableResource.py +46 -26
  52. mage_ai/api/resources/VersionControlProjectResource.py +9 -2
  53. mage_ai/api/resources/WidgetResource.py +1 -1
  54. mage_ai/api/resources/WorkspaceResource.py +1 -1
  55. mage_ai/api/views.py +47 -40
  56. mage_ai/authentication/permissions/seed.py +16 -2
  57. mage_ai/authentication/providers/oidc.py +21 -1
  58. mage_ai/autocomplete/utils.py +13 -9
  59. mage_ai/cache/base.py +1 -1
  60. mage_ai/cache/block.py +18 -12
  61. mage_ai/cache/block_action_object/__init__.py +32 -4
  62. mage_ai/cache/file.py +22 -19
  63. mage_ai/cache/pipeline.py +18 -12
  64. mage_ai/cli/main.py +1 -0
  65. mage_ai/cluster_manager/aws/emr_cluster_manager.py +9 -5
  66. mage_ai/cluster_manager/config.py +2 -2
  67. mage_ai/cluster_manager/manage.py +1 -1
  68. mage_ai/cluster_manager/workspace/base.py +1 -1
  69. mage_ai/command_center/applications/factory.py +10 -7
  70. mage_ai/command_center/files/factory.py +17 -15
  71. mage_ai/command_center/utils.py +25 -13
  72. mage_ai/data/__init__.py +0 -0
  73. mage_ai/data/constants.py +45 -0
  74. mage_ai/data/models/__init__.py +0 -0
  75. mage_ai/data/models/base.py +119 -0
  76. mage_ai/data/models/constants.py +1 -0
  77. mage_ai/data/models/generator.py +115 -0
  78. mage_ai/data/models/manager.py +168 -0
  79. mage_ai/data/models/pyarrow/__init__.py +0 -0
  80. mage_ai/data/models/pyarrow/record_batch.py +55 -0
  81. mage_ai/data/models/pyarrow/shared.py +21 -0
  82. mage_ai/data/models/pyarrow/table.py +8 -0
  83. mage_ai/data/models/reader.py +103 -0
  84. mage_ai/data/models/utils.py +59 -0
  85. mage_ai/data/models/writer.py +91 -0
  86. mage_ai/data/tabular/__init__.py +0 -0
  87. mage_ai/data/tabular/constants.py +23 -0
  88. mage_ai/data/tabular/mocks.py +19 -0
  89. mage_ai/data/tabular/models.py +126 -0
  90. mage_ai/data/tabular/reader.py +602 -0
  91. mage_ai/data/tabular/utils.py +102 -0
  92. mage_ai/data/tabular/writer.py +266 -0
  93. mage_ai/data/variables/__init__.py +0 -0
  94. mage_ai/data/variables/wrapper.py +54 -0
  95. mage_ai/data_cleaner/analysis/charts.py +61 -39
  96. mage_ai/data_cleaner/column_types/column_type_detector.py +53 -31
  97. mage_ai/data_cleaner/estimators/encoders.py +5 -2
  98. mage_ai/data_integrations/utils/scheduler.py +16 -11
  99. mage_ai/data_preparation/decorators.py +1 -0
  100. mage_ai/data_preparation/executors/block_executor.py +237 -155
  101. mage_ai/data_preparation/executors/streaming_pipeline_executor.py +1 -1
  102. mage_ai/data_preparation/git/__init__.py +27 -7
  103. mage_ai/data_preparation/git/api.py +7 -1
  104. mage_ai/data_preparation/git/utils.py +22 -16
  105. mage_ai/data_preparation/logging/logger_manager.py +4 -3
  106. mage_ai/data_preparation/models/block/__init__.py +1542 -878
  107. mage_ai/data_preparation/models/block/data_integration/mixins.py +4 -3
  108. mage_ai/data_preparation/models/block/dynamic/__init__.py +17 -6
  109. mage_ai/data_preparation/models/block/dynamic/child.py +41 -102
  110. mage_ai/data_preparation/models/block/dynamic/constants.py +1 -0
  111. mage_ai/data_preparation/models/block/dynamic/counter.py +296 -0
  112. mage_ai/data_preparation/models/block/dynamic/data.py +16 -0
  113. mage_ai/data_preparation/models/block/dynamic/factory.py +163 -0
  114. mage_ai/data_preparation/models/block/dynamic/models.py +19 -0
  115. mage_ai/data_preparation/models/block/dynamic/shared.py +92 -0
  116. mage_ai/data_preparation/models/block/dynamic/utils.py +291 -168
  117. mage_ai/data_preparation/models/block/dynamic/variables.py +384 -144
  118. mage_ai/data_preparation/models/block/dynamic/wrappers.py +77 -0
  119. mage_ai/data_preparation/models/block/extension/utils.py +10 -1
  120. mage_ai/data_preparation/models/block/global_data_product/__init__.py +10 -1
  121. mage_ai/data_preparation/models/block/integration/__init__.py +6 -2
  122. mage_ai/data_preparation/models/block/outputs.py +722 -0
  123. mage_ai/data_preparation/models/block/platform/mixins.py +7 -8
  124. mage_ai/data_preparation/models/block/r/__init__.py +56 -38
  125. mage_ai/data_preparation/models/block/settings/__init__.py +0 -0
  126. mage_ai/data_preparation/models/block/settings/dynamic/__init__.py +0 -0
  127. mage_ai/data_preparation/models/block/settings/dynamic/constants.py +7 -0
  128. mage_ai/data_preparation/models/block/settings/dynamic/mixins.py +118 -0
  129. mage_ai/data_preparation/models/block/settings/dynamic/models.py +31 -0
  130. mage_ai/data_preparation/models/block/settings/global_data_products/__init__.py +0 -0
  131. mage_ai/data_preparation/models/block/settings/global_data_products/mixins.py +20 -0
  132. mage_ai/data_preparation/models/block/settings/global_data_products/models.py +46 -0
  133. mage_ai/data_preparation/models/block/settings/variables/__init__.py +0 -0
  134. mage_ai/data_preparation/models/block/settings/variables/mixins.py +74 -0
  135. mage_ai/data_preparation/models/block/settings/variables/models.py +49 -0
  136. mage_ai/data_preparation/models/block/spark/mixins.py +2 -1
  137. mage_ai/data_preparation/models/block/sql/__init__.py +30 -5
  138. mage_ai/data_preparation/models/block/sql/utils/shared.py +21 -3
  139. mage_ai/data_preparation/models/block/utils.py +127 -70
  140. mage_ai/data_preparation/models/constants.py +19 -14
  141. mage_ai/data_preparation/models/custom_templates/custom_block_template.py +18 -13
  142. mage_ai/data_preparation/models/custom_templates/custom_pipeline_template.py +33 -16
  143. mage_ai/data_preparation/models/custom_templates/utils.py +1 -1
  144. mage_ai/data_preparation/models/file.py +41 -28
  145. mage_ai/data_preparation/models/global_data_product/__init__.py +88 -58
  146. mage_ai/data_preparation/models/global_hooks/models.py +1 -0
  147. mage_ai/data_preparation/models/interfaces.py +29 -0
  148. mage_ai/data_preparation/models/pipeline.py +365 -180
  149. mage_ai/data_preparation/models/pipelines/integration_pipeline.py +1 -2
  150. mage_ai/data_preparation/models/pipelines/seed.py +1 -1
  151. mage_ai/data_preparation/models/project/__init__.py +66 -18
  152. mage_ai/data_preparation/models/project/constants.py +2 -0
  153. mage_ai/data_preparation/models/triggers/__init__.py +120 -24
  154. mage_ai/data_preparation/models/utils.py +467 -17
  155. mage_ai/data_preparation/models/variable.py +1028 -137
  156. mage_ai/data_preparation/models/variables/__init__.py +0 -0
  157. mage_ai/data_preparation/models/variables/cache.py +149 -0
  158. mage_ai/data_preparation/models/variables/constants.py +72 -0
  159. mage_ai/data_preparation/models/variables/summarizer.py +336 -0
  160. mage_ai/data_preparation/models/variables/utils.py +77 -0
  161. mage_ai/data_preparation/models/widget/__init__.py +63 -41
  162. mage_ai/data_preparation/models/widget/charts.py +40 -27
  163. mage_ai/data_preparation/models/widget/constants.py +2 -0
  164. mage_ai/data_preparation/models/widget/utils.py +3 -3
  165. mage_ai/data_preparation/preferences.py +3 -3
  166. mage_ai/data_preparation/repo_manager.py +55 -21
  167. mage_ai/data_preparation/storage/base_storage.py +2 -2
  168. mage_ai/data_preparation/storage/gcs_storage.py +7 -4
  169. mage_ai/data_preparation/storage/local_storage.py +6 -3
  170. mage_ai/data_preparation/storage/s3_storage.py +5 -2
  171. mage_ai/data_preparation/templates/data_exporters/streaming/oracledb.yaml +8 -0
  172. mage_ai/data_preparation/variable_manager.py +281 -76
  173. mage_ai/io/base.py +3 -2
  174. mage_ai/io/bigquery.py +1 -0
  175. mage_ai/io/redshift.py +7 -5
  176. mage_ai/kernels/__init__.py +0 -0
  177. mage_ai/kernels/models.py +188 -0
  178. mage_ai/kernels/utils.py +169 -0
  179. mage_ai/orchestration/concurrency.py +6 -2
  180. mage_ai/orchestration/db/__init__.py +1 -0
  181. mage_ai/orchestration/db/migrations/versions/0227396a216c_add_userproject_table.py +38 -0
  182. mage_ai/orchestration/db/models/dynamic/__init__.py +0 -0
  183. mage_ai/orchestration/db/models/dynamic/controller.py +67 -0
  184. mage_ai/orchestration/db/models/oauth.py +2 -9
  185. mage_ai/orchestration/db/models/projects.py +10 -0
  186. mage_ai/orchestration/db/models/schedules.py +204 -187
  187. mage_ai/orchestration/db/models/schedules_project_platform.py +18 -12
  188. mage_ai/orchestration/db/models/utils.py +46 -5
  189. mage_ai/orchestration/metrics/pipeline_run.py +8 -9
  190. mage_ai/orchestration/notification/sender.py +1 -0
  191. mage_ai/orchestration/pipeline_scheduler_original.py +32 -8
  192. mage_ai/orchestration/pipeline_scheduler_project_platform.py +1 -1
  193. mage_ai/orchestration/run_status_checker.py +11 -4
  194. mage_ai/orchestration/triggers/api.py +12 -1
  195. mage_ai/presenters/charts/data_sources/base.py +4 -2
  196. mage_ai/presenters/charts/data_sources/block.py +15 -9
  197. mage_ai/presenters/charts/data_sources/chart_code.py +8 -5
  198. mage_ai/presenters/charts/data_sources/constants.py +1 -0
  199. mage_ai/presenters/charts/data_sources/system_metrics.py +22 -0
  200. mage_ai/presenters/interactions/models.py +11 -7
  201. mage_ai/presenters/pages/loaders/pipelines.py +5 -3
  202. mage_ai/presenters/pages/models/page_components/pipeline_schedules.py +3 -1
  203. mage_ai/presenters/utils.py +2 -0
  204. mage_ai/server/api/blocks.py +2 -1
  205. mage_ai/server/api/downloads.py +5 -1
  206. mage_ai/server/api/triggers.py +3 -1
  207. mage_ai/server/constants.py +1 -1
  208. mage_ai/server/frontend_dist/404.html +5 -5
  209. mage_ai/server/frontend_dist/_next/static/UZLabyPgcxtZvp0O0EUUS/_buildManifest.js +1 -0
  210. mage_ai/server/frontend_dist/_next/static/chunks/1376-22de38b4ad008d8a.js +1 -0
  211. mage_ai/server/frontend_dist/_next/static/chunks/{1557-b3502f3f1aa92ac7.js → 1557-25a7d985d5564fd3.js} +1 -1
  212. mage_ai/server/frontend_dist/_next/static/chunks/1668-30b4619b9534519b.js +1 -0
  213. mage_ai/server/frontend_dist/_next/static/chunks/1799-c42db95a015689ee.js +1 -0
  214. mage_ai/server/frontend_dist/_next/static/chunks/2996-2108b53b9d371d8d.js +1 -0
  215. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/3763-61b542dafdbf5754.js → frontend_dist/_next/static/chunks/3763-40780c6d1e4b261d.js} +1 -1
  216. mage_ai/server/frontend_dist/_next/static/chunks/3782-129dd2a2448a2e36.js +1 -0
  217. mage_ai/server/frontend_dist/_next/static/chunks/3958-bcdfa414ccfa1eb2.js +1 -0
  218. mage_ai/server/frontend_dist/_next/static/chunks/4168-97fd1578d1a38315.js +1 -0
  219. mage_ai/server/frontend_dist/_next/static/chunks/4982-fa5a238b139fbdd2.js +1 -0
  220. mage_ai/server/frontend_dist/_next/static/chunks/5699-176f445e1313f001.js +1 -0
  221. mage_ai/server/frontend_dist/_next/static/chunks/7162-7dd03f0f605de721.js +1 -0
  222. mage_ai/server/frontend_dist/_next/static/chunks/7779-68d2b72a90c5f925.js +1 -0
  223. mage_ai/server/frontend_dist/_next/static/chunks/7966-5446a8e43711e2f9.js +1 -0
  224. mage_ai/server/frontend_dist/_next/static/chunks/8023-6c2f172f48dcb99b.js +1 -0
  225. mage_ai/server/frontend_dist/_next/static/chunks/8095-c351b8a735d73e0c.js +1 -0
  226. mage_ai/server/frontend_dist/_next/static/chunks/{main-77fe248a6fbd12d8.js → main-b99d4e30a88d9dc7.js} +1 -1
  227. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-9fe2d9d07c94e968.js +1 -0
  228. mage_ai/server/frontend_dist/_next/static/chunks/pages/{block-layout-14f952f66964022f.js → block-layout-7f4b735c67115df5.js} +1 -1
  229. mage_ai/server/frontend_dist/_next/static/chunks/pages/global-data-products/[...slug]-e7d48e6b0c3068ac.js +1 -0
  230. mage_ai/server/frontend_dist/_next/static/chunks/pages/global-data-products-b943f31f050fc3a4.js +1 -0
  231. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/overview-597b74828bf105db.js → frontend_dist/_next/static/chunks/pages/overview-9f1ac4ec003884f3.js} +1 -1
  232. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/{[...slug]-7181b086c93784d2.js → [...slug]-7e737f6fc7e83e9b.js} +1 -1
  233. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/dashboard-d94488e3f2eeef36.js +1 -0
  234. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-cc641a7fa8473796.js +1 -0
  235. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors/{block-runs-a5c0362763a21fa8.js → block-runs-284309877f3c5a5a.js} +1 -1
  236. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors/block-runtime-26250e5335194ade.js +1 -0
  237. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/monitors-7acc7afc00df17c2.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors-5f4c8128b2413fd8.js} +1 -1
  238. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-4ebfc8e400315dda.js +1 -0
  239. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-e5e0150a256aadb3.js +1 -0
  240. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/account/{profile-3f0df3decc856ee9.js → profile-3ae43c932537b254.js} +1 -1
  241. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-32985f3f7c7dd3ab.js → frontend_dist/_next/static/chunks/pages/settings/platform/preferences-b603d7fe4b175256.js} +1 -1
  242. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-c2e9ef989c8bfa73.js → frontend_dist/_next/static/chunks/pages/settings/platform/settings-319ddbabc239e91b.js} +1 -1
  243. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/permissions/{[...slug]-47b64ced27c24985.js → [...slug]-5c360f72e4498855.js} +1 -1
  244. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{permissions-e5a4d3d815cec25d.js → permissions-fb29fa6c2bd90bb0.js} +1 -1
  245. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-3b76fa959ffa09d3.js +1 -0
  246. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/roles/{[...slug]-379e1ee292504842.js → [...slug]-3b787b42f1093b1f.js} +1 -1
  247. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/roles-0b83fbdd39e85f5b.js +1 -0
  248. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-a1e6950974d643a8.js +1 -0
  249. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/users/{[...slug]-2af9afbe727d88aa.js → [...slug]-0aa019d87db8b0b8.js} +1 -1
  250. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{users-a4db8710f703c729.js → users-88c694d19207f2ec.js} +1 -1
  251. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-31d0d50f7f30462b.js +1 -0
  252. mage_ai/server/frontend_dist/_next/static/chunks/{webpack-d079359c241db804.js → webpack-ac7fdc472bedf682.js} +1 -1
  253. mage_ai/server/frontend_dist/block-layout.html +3 -3
  254. mage_ai/server/frontend_dist/compute.html +6 -6
  255. mage_ai/server/frontend_dist/files.html +6 -6
  256. mage_ai/server/frontend_dist/global-data-products/[...slug].html +6 -6
  257. mage_ai/server/frontend_dist/global-data-products.html +6 -6
  258. mage_ai/server/frontend_dist/global-hooks/[...slug].html +6 -6
  259. mage_ai/server/frontend_dist/global-hooks.html +6 -6
  260. mage_ai/server/frontend_dist/index.html +3 -3
  261. mage_ai/server/frontend_dist/manage/files.html +6 -6
  262. mage_ai/server/frontend_dist/manage/settings.html +6 -6
  263. mage_ai/server/frontend_dist/manage/users/[user].html +6 -6
  264. mage_ai/server/frontend_dist/manage/users/new.html +6 -6
  265. mage_ai/server/frontend_dist/manage/users.html +6 -6
  266. mage_ai/server/frontend_dist/manage.html +6 -6
  267. mage_ai/server/frontend_dist/oauth.html +5 -5
  268. mage_ai/server/frontend_dist/overview.html +6 -6
  269. mage_ai/server/frontend_dist/pipeline-runs.html +6 -6
  270. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +6 -6
  271. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +6 -6
  272. mage_ai/server/frontend_dist/pipelines/[pipeline]/dashboard.html +6 -6
  273. mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +3 -3
  274. mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +6 -6
  275. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +6 -6
  276. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +6 -6
  277. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +6 -6
  278. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +6 -6
  279. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +6 -6
  280. mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +6 -6
  281. mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +6 -6
  282. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +6 -6
  283. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +6 -6
  284. mage_ai/server/frontend_dist/pipelines/[pipeline].html +3 -3
  285. mage_ai/server/frontend_dist/pipelines.html +6 -6
  286. mage_ai/server/frontend_dist/platform/global-hooks/[...slug].html +6 -6
  287. mage_ai/server/frontend_dist/platform/global-hooks.html +6 -6
  288. mage_ai/server/frontend_dist/settings/account/profile.html +6 -6
  289. mage_ai/server/frontend_dist/settings/platform/preferences.html +6 -6
  290. mage_ai/server/frontend_dist/settings/platform/settings.html +6 -6
  291. mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +6 -6
  292. mage_ai/server/frontend_dist/settings/workspace/permissions.html +6 -6
  293. mage_ai/server/frontend_dist/settings/workspace/preferences.html +6 -6
  294. mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +6 -6
  295. mage_ai/server/frontend_dist/settings/workspace/roles.html +6 -6
  296. mage_ai/server/frontend_dist/settings/workspace/sync-data.html +6 -6
  297. mage_ai/server/frontend_dist/settings/workspace/users/[...slug].html +6 -6
  298. mage_ai/server/frontend_dist/settings/workspace/users.html +6 -6
  299. mage_ai/server/frontend_dist/settings.html +3 -3
  300. mage_ai/server/frontend_dist/sign-in.html +12 -12
  301. mage_ai/server/frontend_dist/templates/[...slug].html +6 -6
  302. mage_ai/server/frontend_dist/templates.html +6 -6
  303. mage_ai/server/frontend_dist/terminal.html +6 -6
  304. mage_ai/server/frontend_dist/test.html +3 -3
  305. mage_ai/server/frontend_dist/triggers.html +6 -6
  306. mage_ai/server/frontend_dist/version-control.html +6 -6
  307. mage_ai/server/frontend_dist_base_path_template/404.html +5 -5
  308. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1376-22de38b4ad008d8a.js +1 -0
  309. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{1557-b3502f3f1aa92ac7.js → 1557-25a7d985d5564fd3.js} +1 -1
  310. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1668-30b4619b9534519b.js +1 -0
  311. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1799-c42db95a015689ee.js +1 -0
  312. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2996-2108b53b9d371d8d.js +1 -0
  313. mage_ai/server/{frontend_dist/_next/static/chunks/3763-61b542dafdbf5754.js → frontend_dist_base_path_template/_next/static/chunks/3763-40780c6d1e4b261d.js} +1 -1
  314. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3782-129dd2a2448a2e36.js +1 -0
  315. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3958-bcdfa414ccfa1eb2.js +1 -0
  316. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/4168-97fd1578d1a38315.js +1 -0
  317. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/4982-fa5a238b139fbdd2.js +1 -0
  318. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5699-176f445e1313f001.js +1 -0
  319. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7162-7dd03f0f605de721.js +1 -0
  320. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7779-68d2b72a90c5f925.js +1 -0
  321. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7966-5446a8e43711e2f9.js +1 -0
  322. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/8023-6c2f172f48dcb99b.js +1 -0
  323. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/8095-c351b8a735d73e0c.js +1 -0
  324. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{main-70b78159c2bb3fe1.js → main-384298e9133cec76.js} +1 -1
  325. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-13a578bce3b7f30c.js +1 -0
  326. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{block-layout-14f952f66964022f.js → block-layout-7f4b735c67115df5.js} +1 -1
  327. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/global-data-products/[...slug]-e7d48e6b0c3068ac.js +1 -0
  328. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/global-data-products-b943f31f050fc3a4.js +1 -0
  329. mage_ai/server/{frontend_dist/_next/static/chunks/pages/overview-597b74828bf105db.js → frontend_dist_base_path_template/_next/static/chunks/pages/overview-9f1ac4ec003884f3.js} +1 -1
  330. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/{[...slug]-7181b086c93784d2.js → [...slug]-7e737f6fc7e83e9b.js} +1 -1
  331. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/dashboard-d94488e3f2eeef36.js +1 -0
  332. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-cc641a7fa8473796.js +1 -0
  333. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/monitors/{block-runs-a5c0362763a21fa8.js → block-runs-284309877f3c5a5a.js} +1 -1
  334. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/monitors/block-runtime-26250e5335194ade.js +1 -0
  335. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors-7acc7afc00df17c2.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/monitors-5f4c8128b2413fd8.js} +1 -1
  336. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-4ebfc8e400315dda.js +1 -0
  337. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-e5e0150a256aadb3.js +1 -0
  338. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/account/{profile-3f0df3decc856ee9.js → profile-3ae43c932537b254.js} +1 -1
  339. mage_ai/server/{frontend_dist/_next/static/chunks/pages/settings/platform/preferences-32985f3f7c7dd3ab.js → frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-b603d7fe4b175256.js} +1 -1
  340. mage_ai/server/{frontend_dist/_next/static/chunks/pages/settings/platform/settings-c2e9ef989c8bfa73.js → frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-319ddbabc239e91b.js} +1 -1
  341. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/permissions/{[...slug]-47b64ced27c24985.js → [...slug]-5c360f72e4498855.js} +1 -1
  342. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/{permissions-e5a4d3d815cec25d.js → permissions-fb29fa6c2bd90bb0.js} +1 -1
  343. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-3b76fa959ffa09d3.js +1 -0
  344. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/roles/{[...slug]-379e1ee292504842.js → [...slug]-3b787b42f1093b1f.js} +1 -1
  345. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/roles-0b83fbdd39e85f5b.js +1 -0
  346. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-a1e6950974d643a8.js +1 -0
  347. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/users/{[...slug]-2af9afbe727d88aa.js → [...slug]-0aa019d87db8b0b8.js} +1 -1
  348. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/{users-a4db8710f703c729.js → users-88c694d19207f2ec.js} +1 -1
  349. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-31d0d50f7f30462b.js +1 -0
  350. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{webpack-68c003fb6a175cd7.js → webpack-481689d9989710cd.js} +1 -1
  351. mage_ai/server/frontend_dist_base_path_template/_next/static/kcptwoOU-JJJg6Vwpkfmx/_buildManifest.js +1 -0
  352. mage_ai/server/frontend_dist_base_path_template/block-layout.html +3 -3
  353. mage_ai/server/frontend_dist_base_path_template/compute.html +6 -6
  354. mage_ai/server/frontend_dist_base_path_template/files.html +6 -6
  355. mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +6 -6
  356. mage_ai/server/frontend_dist_base_path_template/global-data-products.html +6 -6
  357. mage_ai/server/frontend_dist_base_path_template/global-hooks/[...slug].html +6 -6
  358. mage_ai/server/frontend_dist_base_path_template/global-hooks.html +6 -6
  359. mage_ai/server/frontend_dist_base_path_template/index.html +3 -3
  360. mage_ai/server/frontend_dist_base_path_template/manage/files.html +6 -6
  361. mage_ai/server/frontend_dist_base_path_template/manage/settings.html +6 -6
  362. mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +6 -6
  363. mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +6 -6
  364. mage_ai/server/frontend_dist_base_path_template/manage/users.html +6 -6
  365. mage_ai/server/frontend_dist_base_path_template/manage.html +6 -6
  366. mage_ai/server/frontend_dist_base_path_template/oauth.html +5 -5
  367. mage_ai/server/frontend_dist_base_path_template/overview.html +6 -6
  368. mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +6 -6
  369. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +6 -6
  370. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +6 -6
  371. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +6 -6
  372. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +3 -3
  373. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +6 -6
  374. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +6 -6
  375. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +6 -6
  376. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +6 -6
  377. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +6 -6
  378. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +6 -6
  379. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +6 -6
  380. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +6 -6
  381. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +6 -6
  382. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +6 -6
  383. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +3 -3
  384. mage_ai/server/frontend_dist_base_path_template/pipelines.html +6 -6
  385. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks/[...slug].html +6 -6
  386. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks.html +6 -6
  387. mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +6 -6
  388. mage_ai/server/frontend_dist_base_path_template/settings/platform/preferences.html +6 -6
  389. mage_ai/server/frontend_dist_base_path_template/settings/platform/settings.html +6 -6
  390. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +6 -6
  391. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +6 -6
  392. mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +6 -6
  393. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +6 -6
  394. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +6 -6
  395. mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +6 -6
  396. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +6 -6
  397. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +6 -6
  398. mage_ai/server/frontend_dist_base_path_template/settings.html +3 -3
  399. mage_ai/server/frontend_dist_base_path_template/sign-in.html +12 -12
  400. mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +6 -6
  401. mage_ai/server/frontend_dist_base_path_template/templates.html +6 -6
  402. mage_ai/server/frontend_dist_base_path_template/terminal.html +6 -6
  403. mage_ai/server/frontend_dist_base_path_template/test.html +3 -3
  404. mage_ai/server/frontend_dist_base_path_template/triggers.html +6 -6
  405. mage_ai/server/frontend_dist_base_path_template/version-control.html +6 -6
  406. mage_ai/server/kernel_output_parser.py +4 -1
  407. mage_ai/server/scheduler_manager.py +9 -0
  408. mage_ai/server/server.py +35 -31
  409. mage_ai/server/utils/custom_output.py +284 -0
  410. mage_ai/server/utils/execute_custom_code.py +245 -0
  411. mage_ai/server/utils/output_display.py +123 -289
  412. mage_ai/server/websocket_server.py +116 -69
  413. mage_ai/services/k8s/config.py +23 -0
  414. mage_ai/services/k8s/job_manager.py +6 -1
  415. mage_ai/services/ssh/aws/emr/utils.py +8 -8
  416. mage_ai/settings/keys/auth.py +1 -0
  417. mage_ai/settings/platform/__init__.py +159 -38
  418. mage_ai/settings/platform/constants.py +5 -0
  419. mage_ai/settings/platform/utils.py +53 -10
  420. mage_ai/settings/repo.py +26 -12
  421. mage_ai/settings/server.py +128 -37
  422. mage_ai/shared/array.py +24 -1
  423. mage_ai/shared/complex.py +45 -0
  424. mage_ai/shared/config.py +2 -1
  425. mage_ai/shared/custom_logger.py +11 -0
  426. mage_ai/shared/dates.py +10 -6
  427. mage_ai/shared/files.py +63 -8
  428. mage_ai/shared/hash.py +33 -9
  429. mage_ai/shared/io.py +9 -5
  430. mage_ai/shared/models.py +82 -24
  431. mage_ai/shared/outputs.py +87 -0
  432. mage_ai/shared/parsers.py +141 -15
  433. mage_ai/shared/path_fixer.py +11 -7
  434. mage_ai/shared/singletons/__init__.py +0 -0
  435. mage_ai/shared/singletons/base.py +47 -0
  436. mage_ai/shared/singletons/memory.py +38 -0
  437. mage_ai/shared/strings.py +34 -1
  438. mage_ai/shared/yaml.py +24 -0
  439. mage_ai/streaming/sinks/oracledb.py +57 -0
  440. mage_ai/streaming/sinks/sink_factory.py +4 -0
  441. mage_ai/system/__init__.py +0 -0
  442. mage_ai/system/constants.py +14 -0
  443. mage_ai/system/memory/__init__.py +0 -0
  444. mage_ai/system/memory/constants.py +1 -0
  445. mage_ai/system/memory/manager.py +174 -0
  446. mage_ai/system/memory/presenters.py +158 -0
  447. mage_ai/system/memory/process.py +216 -0
  448. mage_ai/system/memory/samples.py +13 -0
  449. mage_ai/system/memory/utils.py +656 -0
  450. mage_ai/system/memory/wrappers.py +177 -0
  451. mage_ai/system/models.py +58 -0
  452. mage_ai/system/storage/__init__.py +0 -0
  453. mage_ai/system/storage/utils.py +29 -0
  454. mage_ai/tests/api/endpoints/mixins.py +2 -2
  455. mage_ai/tests/api/endpoints/test_blocks.py +2 -1
  456. mage_ai/tests/api/endpoints/test_custom_designs.py +4 -4
  457. mage_ai/tests/api/endpoints/test_pipeline_runs.py +2 -2
  458. mage_ai/tests/api/endpoints/test_projects.py +2 -1
  459. mage_ai/tests/api/operations/base/test_base.py +27 -27
  460. mage_ai/tests/api/operations/base/test_base_with_user_authentication.py +27 -27
  461. mage_ai/tests/api/operations/base/test_base_with_user_permissions.py +23 -23
  462. mage_ai/tests/api/operations/test_syncs.py +6 -4
  463. mage_ai/tests/api/resources/test_pipeline_resource.py +9 -2
  464. mage_ai/tests/authentication/providers/test_oidc.py +59 -0
  465. mage_ai/tests/base_test.py +2 -2
  466. mage_ai/tests/data/__init__.py +0 -0
  467. mage_ai/tests/data/models/__init__.py +0 -0
  468. mage_ai/tests/data_preparation/executors/test_block_executor.py +23 -16
  469. mage_ai/tests/data_preparation/git/test_git.py +4 -1
  470. mage_ai/tests/data_preparation/models/block/dynamic/test_combos.py +305 -0
  471. mage_ai/tests/data_preparation/models/block/dynamic/test_counter.py +212 -0
  472. mage_ai/tests/data_preparation/models/block/dynamic/test_factory.py +360 -0
  473. mage_ai/tests/data_preparation/models/block/dynamic/test_variables.py +332 -0
  474. mage_ai/tests/data_preparation/models/block/hook/test_hook_block.py +2 -2
  475. mage_ai/tests/data_preparation/models/block/platform/test_mixins.py +1 -1
  476. mage_ai/tests/data_preparation/models/block/sql/utils/test_shared.py +26 -1
  477. mage_ai/tests/data_preparation/models/block/test_global_data_product.py +3 -2
  478. mage_ai/tests/data_preparation/models/custom_templates/test_utils.py +5 -4
  479. mage_ai/tests/data_preparation/models/global_hooks/test_hook.py +3 -0
  480. mage_ai/tests/data_preparation/models/global_hooks/test_predicates.py +9 -3
  481. mage_ai/tests/data_preparation/models/test_block.py +115 -120
  482. mage_ai/tests/data_preparation/models/test_blocks_helper.py +114 -0
  483. mage_ai/tests/data_preparation/models/test_global_data_product.py +41 -24
  484. mage_ai/tests/data_preparation/models/test_pipeline.py +9 -6
  485. mage_ai/tests/data_preparation/models/test_project.py +4 -1
  486. mage_ai/tests/data_preparation/models/test_utils.py +80 -0
  487. mage_ai/tests/data_preparation/models/test_variable.py +242 -69
  488. mage_ai/tests/data_preparation/models/variables/__init__.py +0 -0
  489. mage_ai/tests/data_preparation/models/variables/test_summarizer.py +481 -0
  490. mage_ai/tests/data_preparation/storage/shared/__init__.py +0 -0
  491. mage_ai/tests/data_preparation/test_repo_manager.py +6 -7
  492. mage_ai/tests/data_preparation/test_variable_manager.py +57 -48
  493. mage_ai/tests/factory.py +64 -43
  494. mage_ai/tests/orchestration/db/models/test_schedules.py +3 -3
  495. mage_ai/tests/orchestration/db/models/test_schedules_dynamic_blocks.py +279 -0
  496. mage_ai/tests/orchestration/test_pipeline_scheduler.py +1 -0
  497. mage_ai/tests/orchestration/triggers/test_global_data_product.py +3 -2
  498. mage_ai/tests/orchestration/triggers/test_utils.py +3 -2
  499. mage_ai/tests/services/k8s/test_job_manager.py +18 -0
  500. mage_ai/tests/streaming/sinks/test_oracledb.py +38 -0
  501. mage_ai/tests/test_shared.py +61 -0
  502. mage_ai/usage_statistics/logger.py +7 -2
  503. mage_ai/utils/code.py +33 -19
  504. {mage_ai-0.9.70.dist-info → mage_ai-0.9.71.dist-info}/METADATA +5 -2
  505. {mage_ai-0.9.70.dist-info → mage_ai-0.9.71.dist-info}/RECORD +513 -417
  506. mage_ai/data_preparation/models/global_data_product/constants.py +0 -6
  507. mage_ai/server/frontend_dist/_next/static/RhDiJSkcjCsh4xxX4BFBk/_buildManifest.js +0 -1
  508. mage_ai/server/frontend_dist/_next/static/chunks/2631-b9f9bea3f1cf906d.js +0 -1
  509. mage_ai/server/frontend_dist/_next/static/chunks/3782-ef4cd4f0b52072d0.js +0 -1
  510. mage_ai/server/frontend_dist/_next/static/chunks/4783-422429203610c318.js +0 -1
  511. mage_ai/server/frontend_dist/_next/static/chunks/5699-6d708c6b2153ea08.js +0 -1
  512. mage_ai/server/frontend_dist/_next/static/chunks/635-0d6b7c8804bcd2dc.js +0 -1
  513. mage_ai/server/frontend_dist/_next/static/chunks/7022-0d52dd8868621fb0.js +0 -1
  514. mage_ai/server/frontend_dist/_next/static/chunks/7361-8a23dd8360593e7a.js +0 -1
  515. mage_ai/server/frontend_dist/_next/static/chunks/7966-b9b85ba10667e654.js +0 -1
  516. mage_ai/server/frontend_dist/_next/static/chunks/8095-bdce03896ef9639a.js +0 -1
  517. mage_ai/server/frontend_dist/_next/static/chunks/8146-6bed4e7401e067e6.js +0 -1
  518. mage_ai/server/frontend_dist/_next/static/chunks/9265-d2a1aaec75ec69b8.js +0 -1
  519. mage_ai/server/frontend_dist/_next/static/chunks/9440-4069842b90d4b801.js +0 -1
  520. mage_ai/server/frontend_dist/_next/static/chunks/9832-67896490f6e8a014.js +0 -1
  521. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-2a69553d8c6eeb53.js +0 -1
  522. mage_ai/server/frontend_dist/_next/static/chunks/pages/global-data-products/[...slug]-591abd392dc50ed4.js +0 -1
  523. mage_ai/server/frontend_dist/_next/static/chunks/pages/global-data-products-78e8e88f2a757a18.js +0 -1
  524. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/dashboard-95ffcd3e2b27e567.js +0 -1
  525. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-b645a6d13ab9fe3a.js +0 -1
  526. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors/block-runtime-1ed9045b2f1dfd65.js +0 -1
  527. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1417ad1c821d720a.js +0 -1
  528. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-59aca25a5b1d3998.js +0 -1
  529. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-349af617d05f001b.js +0 -1
  530. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/roles-36fa165a48af586b.js +0 -1
  531. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-60d01d3887e31136.js +0 -1
  532. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-3433c8b22e8342aa.js +0 -1
  533. mage_ai/server/frontend_dist_base_path_template/_next/static/TdpLLFome13qvM0gXvpHs/_buildManifest.js +0 -1
  534. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2631-b9f9bea3f1cf906d.js +0 -1
  535. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3782-ef4cd4f0b52072d0.js +0 -1
  536. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/4783-422429203610c318.js +0 -1
  537. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5699-6d708c6b2153ea08.js +0 -1
  538. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/635-0d6b7c8804bcd2dc.js +0 -1
  539. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7022-0d52dd8868621fb0.js +0 -1
  540. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7361-8a23dd8360593e7a.js +0 -1
  541. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7966-b9b85ba10667e654.js +0 -1
  542. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/8095-bdce03896ef9639a.js +0 -1
  543. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/8146-6bed4e7401e067e6.js +0 -1
  544. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9265-d2a1aaec75ec69b8.js +0 -1
  545. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9440-4069842b90d4b801.js +0 -1
  546. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9832-67896490f6e8a014.js +0 -1
  547. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-2a69553d8c6eeb53.js +0 -1
  548. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/global-data-products/[...slug]-591abd392dc50ed4.js +0 -1
  549. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/global-data-products-78e8e88f2a757a18.js +0 -1
  550. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/dashboard-95ffcd3e2b27e567.js +0 -1
  551. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-b645a6d13ab9fe3a.js +0 -1
  552. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/monitors/block-runtime-1ed9045b2f1dfd65.js +0 -1
  553. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1417ad1c821d720a.js +0 -1
  554. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-59aca25a5b1d3998.js +0 -1
  555. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-349af617d05f001b.js +0 -1
  556. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/roles-36fa165a48af586b.js +0 -1
  557. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-60d01d3887e31136.js +0 -1
  558. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-3433c8b22e8342aa.js +0 -1
  559. mage_ai/shared/memory.py +0 -90
  560. mage_ai/tests/data_preparation/models/block/dynamic/test_dynamic_helpers.py +0 -48
  561. /mage_ai/{tests/data_preparation/shared → ai/utils}/__init__.py +0 -0
  562. /mage_ai/server/frontend_dist/_next/static/{RhDiJSkcjCsh4xxX4BFBk → UZLabyPgcxtZvp0O0EUUS}/_ssgManifest.js +0 -0
  563. /mage_ai/server/frontend_dist_base_path_template/_next/static/{TdpLLFome13qvM0gXvpHs → kcptwoOU-JJJg6Vwpkfmx}/_ssgManifest.js +0 -0
  564. /mage_ai/tests/data_preparation/{shared → storage/shared}/test_secrets.py +0 -0
  565. {mage_ai-0.9.70.dist-info → mage_ai-0.9.71.dist-info}/LICENSE +0 -0
  566. {mage_ai-0.9.70.dist-info → mage_ai-0.9.71.dist-info}/WHEEL +0 -0
  567. {mage_ai-0.9.70.dist-info → mage_ai-0.9.71.dist-info}/entry_points.txt +0 -0
  568. {mage_ai-0.9.70.dist-info → mage_ai-0.9.71.dist-info}/top_level.txt +0 -0
@@ -14,7 +14,18 @@ from inspect import Parameter, isfunction, signature
14
14
  from logging import Logger
15
15
  from pathlib import Path
16
16
  from queue import Queue
17
- from typing import Any, Callable, Dict, Generator, List, Set, Tuple, Union
17
+ from typing import (
18
+ Any,
19
+ Callable,
20
+ Dict,
21
+ Generator,
22
+ Iterable,
23
+ List,
24
+ Optional,
25
+ Set,
26
+ Tuple,
27
+ Union,
28
+ )
18
29
 
19
30
  import inflection
20
31
  import pandas as pd
@@ -25,7 +36,8 @@ from jinja2 import Template
25
36
 
26
37
  import mage_ai.data_preparation.decorators
27
38
  from mage_ai.cache.block import BlockCache
28
- from mage_ai.data_cleaner.shared.utils import is_geo_dataframe, is_spark_dataframe
39
+ from mage_ai.data.constants import InputDataType
40
+ from mage_ai.data.tabular.models import BatchSettings
29
41
  from mage_ai.data_integrations.sources.constants import SQL_SOURCES_MAPPING
30
42
  from mage_ai.data_preparation.logging.logger import DictLogger
31
43
  from mage_ai.data_preparation.logging.logger_manager_factory import LoggerManagerFactory
@@ -43,6 +55,7 @@ from mage_ai.data_preparation.models.block.dynamic.utils import (
43
55
  uuid_for_output_variables,
44
56
  )
45
57
  from mage_ai.data_preparation.models.block.dynamic.variables import (
58
+ LazyVariableSet,
46
59
  delete_variable_objects_for_dynamic_child,
47
60
  fetch_input_variables_for_dynamic_upstream_blocks,
48
61
  get_outputs_for_dynamic_block,
@@ -51,10 +64,26 @@ from mage_ai.data_preparation.models.block.dynamic.variables import (
51
64
  )
52
65
  from mage_ai.data_preparation.models.block.errors import HasDownstreamDependencies
53
66
  from mage_ai.data_preparation.models.block.extension.utils import handle_run_tests
67
+ from mage_ai.data_preparation.models.block.outputs import (
68
+ format_output_data,
69
+ get_outputs_for_display_async,
70
+ get_outputs_for_display_dynamic_block,
71
+ get_outputs_for_display_sync,
72
+ )
54
73
  from mage_ai.data_preparation.models.block.platform.mixins import (
55
74
  ProjectPlatformAccessible,
56
75
  )
57
76
  from mage_ai.data_preparation.models.block.platform.utils import from_another_project
77
+ from mage_ai.data_preparation.models.block.settings.dynamic.mixins import DynamicMixin
78
+ from mage_ai.data_preparation.models.block.settings.global_data_products.mixins import (
79
+ GlobalDataProductsMixin,
80
+ )
81
+ from mage_ai.data_preparation.models.block.settings.variables.mixins import (
82
+ VariablesMixin,
83
+ )
84
+ from mage_ai.data_preparation.models.block.settings.variables.models import (
85
+ ChunkKeyTypeUnion,
86
+ )
58
87
  from mage_ai.data_preparation.models.block.spark.mixins import SparkBlock
59
88
  from mage_ai.data_preparation.models.block.utils import (
60
89
  clean_name,
@@ -64,13 +93,14 @@ from mage_ai.data_preparation.models.block.utils import (
64
93
  is_valid_print_variable,
65
94
  output_variables,
66
95
  )
67
- from mage_ai.data_preparation.models.constants import (
96
+ from mage_ai.data_preparation.models.constants import ( # PIPELINES_FOLDER,
68
97
  BLOCK_LANGUAGE_TO_FILE_EXTENSION,
69
98
  CALLBACK_STATUSES,
70
99
  CUSTOM_EXECUTION_BLOCK_TYPES,
71
100
  DATAFRAME_ANALYSIS_MAX_COLUMNS,
72
101
  DATAFRAME_ANALYSIS_MAX_ROWS,
73
102
  DATAFRAME_SAMPLE_COUNT_PREVIEW,
103
+ DYNAMIC_CHILD_BLOCK_SAMPLE_COUNT_PREVIEW,
74
104
  FILE_EXTENSION_TO_BLOCK_LANGUAGE,
75
105
  NON_PIPELINE_EXECUTABLE_BLOCK_TYPES,
76
106
  BlockColor,
@@ -82,18 +112,36 @@ from mage_ai.data_preparation.models.constants import (
82
112
  PipelineType,
83
113
  )
84
114
  from mage_ai.data_preparation.models.file import File
85
- from mage_ai.data_preparation.models.variable import VariableType
115
+ from mage_ai.data_preparation.models.utils import is_basic_iterable, warn_for_repo_path
116
+ from mage_ai.data_preparation.models.variable import Variable
117
+ from mage_ai.data_preparation.models.variables.cache import (
118
+ AggregateInformation,
119
+ AggregateInformationData,
120
+ InformationData,
121
+ VariableAggregateCache,
122
+ )
123
+ from mage_ai.data_preparation.models.variables.constants import (
124
+ VariableAggregateDataType,
125
+ VariableAggregateSummaryGroupType,
126
+ VariableType,
127
+ )
128
+ from mage_ai.data_preparation.models.variables.summarizer import (
129
+ aggregate_summary_info_for_all_variables,
130
+ get_aggregate_summary_info,
131
+ )
86
132
  from mage_ai.data_preparation.repo_manager import RepoConfig
87
133
  from mage_ai.data_preparation.shared.stream import StreamToLogger
88
134
  from mage_ai.data_preparation.shared.utils import get_template_vars
89
135
  from mage_ai.data_preparation.templates.data_integrations.utils import get_templates
90
136
  from mage_ai.data_preparation.templates.template import load_template
91
- from mage_ai.server.kernel_output_parser import DataType
137
+ from mage_ai.data_preparation.variable_manager import VariableManager
138
+ from mage_ai.io.base import ExportWritePolicy
92
139
  from mage_ai.services.spark.config import SparkConfig
93
140
  from mage_ai.services.spark.spark import SPARK_ENABLED, get_spark_session
94
141
  from mage_ai.settings.platform.constants import project_platform_activated
95
- from mage_ai.settings.repo import get_repo_path
96
- from mage_ai.shared.array import unique_by
142
+ from mage_ai.settings.repo import base_repo_path_directory_name, get_repo_path
143
+ from mage_ai.settings.server import VARIABLE_DATA_OUTPUT_META_CACHE
144
+ from mage_ai.shared.array import is_iterable, unique_by
97
145
  from mage_ai.shared.constants import ENV_DEV, ENV_TEST
98
146
  from mage_ai.shared.custom_logger import DX_PRINTER
99
147
  from mage_ai.shared.environments import get_env, is_debug
@@ -110,6 +158,10 @@ from mage_ai.shared.strings import format_enum
110
158
  from mage_ai.shared.utils import clean_name as clean_name_orig
111
159
  from mage_ai.shared.utils import is_spark_env
112
160
 
161
+ # from mage_ai.system.memory.manager import MemoryManager
162
+ from mage_ai.system.memory.wrappers import execute_with_memory_tracking
163
+ from mage_ai.system.models import ResourceUsage
164
+
113
165
  PYTHON_COMMAND = 'python3'
114
166
  BLOCK_EXISTS_ERROR = '[ERR_BLOCK_EXISTS]'
115
167
 
@@ -174,8 +226,11 @@ async def run_blocks(
174
226
  while not blocks.empty():
175
227
  block = blocks.get()
176
228
 
177
- if block.type in NON_PIPELINE_EXECUTABLE_BLOCK_TYPES or \
178
- not run_sensors and block.type == BlockType.SENSOR:
229
+ if (
230
+ block.type in NON_PIPELINE_EXECUTABLE_BLOCK_TYPES
231
+ or not run_sensors
232
+ and block.type == BlockType.SENSOR
233
+ ):
179
234
  continue
180
235
 
181
236
  if tries_by_block_uuid.get(block.uuid, None) is None:
@@ -290,7 +345,14 @@ def run_blocks_sync(
290
345
  blocks.put(downstream_block)
291
346
 
292
347
 
293
- class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
348
+ class Block(
349
+ DataIntegrationMixin,
350
+ SparkBlock,
351
+ ProjectPlatformAccessible,
352
+ DynamicMixin,
353
+ GlobalDataProductsMixin,
354
+ VariablesMixin,
355
+ ):
294
356
  def __init__(
295
357
  self,
296
358
  name: str,
@@ -376,6 +438,13 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
376
438
  # Needs to after self._project_platform_activated = None
377
439
  self.configuration = configuration
378
440
 
441
+ self.resource_usage = None
442
+ self._store_variables_in_block_function: Optional[
443
+ Callable[..., Optional[List[VariableType]]]
444
+ ] = None
445
+
446
+ self._variable_aggregate_cache = None
447
+
379
448
  @property
380
449
  def uuid(self) -> str:
381
450
  return self._uuid
@@ -449,6 +518,166 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
449
518
  )
450
519
  return None
451
520
 
521
+ @property
522
+ def variable_manager(self) -> VariableManager:
523
+ if not self.pipeline:
524
+ return
525
+ return self.pipeline.variable_manager
526
+
527
+ @property
528
+ def pipeline_uuid(self) -> str:
529
+ return self.pipeline.uuid if self.pipeline else ''
530
+
531
+ def __load_variable_aggregate_cache(self, variable_uuid: str) -> VariableAggregateCache:
532
+ if not self._variable_aggregate_cache:
533
+ self._variable_aggregate_cache = {variable_uuid: VariableAggregateCache()}
534
+
535
+ if variable_uuid not in self._variable_aggregate_cache:
536
+ self._variable_aggregate_cache[variable_uuid] = VariableAggregateCache()
537
+
538
+ return self._variable_aggregate_cache[variable_uuid]
539
+
540
+ def get_variable_aggregate_cache(
541
+ self,
542
+ variable_uuid: str,
543
+ data_type: VariableAggregateDataType,
544
+ default_group_type: Optional[VariableAggregateSummaryGroupType] = None,
545
+ group_type: Optional[VariableAggregateSummaryGroupType] = None,
546
+ infer_group_type: Optional[bool] = None,
547
+ partition: Optional[str] = None,
548
+ ) -> Optional[Union[AggregateInformationData, InformationData]]:
549
+ if not VARIABLE_DATA_OUTPUT_META_CACHE:
550
+ return
551
+
552
+ cache = self.__load_variable_aggregate_cache(variable_uuid)
553
+ cache = VariableAggregateCache.load(cache)
554
+
555
+ if infer_group_type:
556
+ group_type = (
557
+ VariableAggregateSummaryGroupType.DYNAMIC
558
+ if is_dynamic_block_child(self)
559
+ else VariableAggregateSummaryGroupType.PARTS
560
+ )
561
+
562
+ keys = [v.value for v in [group_type, data_type] if v is not None]
563
+ value = functools.reduce(getattr, keys, cache)
564
+
565
+ if not value:
566
+ cache_new = get_aggregate_summary_info(
567
+ self.variable_manager,
568
+ self.pipeline_uuid,
569
+ self.uuid,
570
+ variable_uuid,
571
+ data_type,
572
+ default_group_type=default_group_type,
573
+ group_type=group_type,
574
+ partition=partition,
575
+ )
576
+ group_value_use = (group_type.value if group_type else None) or (
577
+ default_group_type.value if default_group_type else None
578
+ )
579
+ if group_value_use is not None:
580
+ cache_group = AggregateInformation.load(getattr(cache, group_value_use))
581
+ cache_group_new = AggregateInformation.load(getattr(cache_new, group_value_use))
582
+ if cache_group_new:
583
+ for data in VariableAggregateDataType:
584
+ val = getattr(cache_group, data.value)
585
+ val_new = getattr(cache_group_new, data.value)
586
+ cache_group.update_attributes(**{
587
+ data.value: val_new or val,
588
+ })
589
+ cache.update_attributes(**{
590
+ group_value_use: AggregateInformation.load(cache_group)
591
+ })
592
+
593
+ for data in VariableAggregateDataType:
594
+ val = getattr(cache, data.value)
595
+ val_new = getattr(cache_new, data.value)
596
+ cache.update_attributes(**{
597
+ data.value: val_new or val,
598
+ })
599
+
600
+ cache = VariableAggregateCache.load(cache)
601
+ self._variable_aggregate_cache = merge_dict(
602
+ self._variable_aggregate_cache or {},
603
+ {variable_uuid: cache},
604
+ )
605
+ value = functools.reduce(getattr, keys, cache)
606
+
607
+ return value
608
+
609
+ def get_resource_usage(
610
+ self,
611
+ block_uuid: Optional[str] = None,
612
+ index: Optional[int] = None,
613
+ partition: Optional[str] = None,
614
+ variable_uuid: Optional[str] = None,
615
+ ) -> Optional[ResourceUsage]:
616
+ try:
617
+ if not VARIABLE_DATA_OUTPUT_META_CACHE:
618
+ variable = self.get_variable_object(
619
+ block_uuid or self.uuid, partition=partition, variable_uuid=variable_uuid
620
+ )
621
+ return variable.get_resource_usage(index=index)
622
+
623
+ values = self.get_variable_aggregate_cache(
624
+ variable_uuid,
625
+ VariableAggregateDataType.RESOURCE_USAGE,
626
+ infer_group_type=index is not None,
627
+ partition=partition,
628
+ )
629
+
630
+ if index is not None:
631
+ if values and isinstance(values, list) and len(values) > index:
632
+ values = values[index]
633
+ else:
634
+ values = values
635
+
636
+ if isinstance(values, Iterable) and len(values) >= 1:
637
+ values = values[0]
638
+
639
+ return values
640
+ except Exception as err:
641
+ print(f'[ERROR] Block.get_resource_usage: {err}')
642
+ return ResourceUsage()
643
+
644
+ def get_analysis(
645
+ self,
646
+ block_uuid: Optional[str] = None,
647
+ index: Optional[int] = None,
648
+ partition: Optional[str] = None,
649
+ variable_uuid: Optional[str] = None,
650
+ ) -> Optional[Dict]:
651
+ try:
652
+ if not VARIABLE_DATA_OUTPUT_META_CACHE:
653
+ variable = self.get_variable_object(
654
+ block_uuid or self.uuid, partition=partition, variable_uuid=variable_uuid
655
+ )
656
+ return variable.get_analysis(index=index)
657
+
658
+ values = self.get_variable_aggregate_cache(
659
+ variable_uuid,
660
+ VariableAggregateDataType.STATISTICS,
661
+ infer_group_type=index is not None,
662
+ partition=partition,
663
+ )
664
+
665
+ value = None
666
+ if index is not None:
667
+ if values and isinstance(values, list) and len(values) > index:
668
+ value = values[index]
669
+ else:
670
+ value = values
671
+
672
+ if isinstance(value, Iterable) and len(value) >= 1:
673
+ value = value[0]
674
+
675
+ if value is not None:
676
+ return dict(statistics=value.to_dict() if value else {})
677
+ except Exception as err:
678
+ print(f'[ERROR] Block.get_analysis: {err}')
679
+ return {}
680
+
452
681
  async def content_async(self) -> str:
453
682
  if self.replicated_block and self.replicated_block_object:
454
683
  self._content = await self.replicated_block_object.content_async()
@@ -483,32 +712,42 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
483
712
  upstream_block_uuids = self.upstream_block_uuids
484
713
 
485
714
  if outputs_from_input_vars is None:
486
-
487
- if BlockLanguage.SQL == self.language and any([is_dynamic_block(
488
- upstream_block,
489
- ) or is_dynamic_block_child(
490
- upstream_block,
491
- ) for upstream_block in self.upstream_blocks]):
492
- outputs_from_input_vars, _kwargs_vars, upstream_block_uuids = \
493
- fetch_input_variables_for_dynamic_upstream_blocks(
494
- self,
495
- None,
496
- dynamic_block_index=dynamic_block_index,
497
- dynamic_block_indexes=dynamic_block_indexes,
498
- execution_partition=execution_partition,
499
- from_notebook=from_notebook,
500
- global_vars=variables,
501
- )
715
+ if BlockLanguage.SQL == self.language and any([
716
+ is_dynamic_block(
717
+ upstream_block,
718
+ )
719
+ or is_dynamic_block_child(
720
+ upstream_block,
721
+ )
722
+ for upstream_block in self.upstream_blocks
723
+ ]):
724
+ (
725
+ outputs_from_input_vars,
726
+ _kwargs_vars,
727
+ upstream_block_uuids,
728
+ ) = fetch_input_variables_for_dynamic_upstream_blocks(
729
+ self,
730
+ None,
731
+ dynamic_block_index=dynamic_block_index,
732
+ dynamic_block_indexes=dynamic_block_indexes,
733
+ execution_partition=execution_partition,
734
+ from_notebook=from_notebook,
735
+ global_vars=variables,
736
+ )
502
737
  else:
503
- outputs_from_input_vars, _input_vars, _kwargs_vars, upstream_block_uuids = \
504
- self.__get_outputs_from_input_vars(
505
- dynamic_block_index=dynamic_block_index,
506
- dynamic_block_indexes=dynamic_block_indexes,
507
- dynamic_upstream_block_uuids=dynamic_upstream_block_uuids,
508
- execution_partition=execution_partition,
509
- from_notebook=from_notebook,
510
- global_vars=variables,
511
- )
738
+ (
739
+ outputs_from_input_vars,
740
+ _input_vars,
741
+ _kwargs_vars,
742
+ upstream_block_uuids,
743
+ ) = self.__get_outputs_from_input_vars(
744
+ dynamic_block_index=dynamic_block_index,
745
+ dynamic_block_indexes=dynamic_block_indexes,
746
+ dynamic_upstream_block_uuids=dynamic_upstream_block_uuids,
747
+ execution_partition=execution_partition,
748
+ from_notebook=from_notebook,
749
+ global_vars=variables,
750
+ )
512
751
 
513
752
  return hydrate_block_outputs(
514
753
  content,
@@ -524,7 +763,11 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
524
763
  if BlockLanguage.YAML == self.language:
525
764
  content = await self.content_async()
526
765
  if content:
527
- text = self.interpolate_content(content)
766
+ try:
767
+ text = self.interpolate_content(content)
768
+ except Exception:
769
+ traceback.print_exc()
770
+ text = content
528
771
  settings = yaml.safe_load(text)
529
772
  uuid = settings.get('source') or settings.get('destination')
530
773
  mapping = grouped_templates.get(uuid) or {}
@@ -550,11 +793,14 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
550
793
 
551
794
  di_metadata = merge_dict(
552
795
  extract(mapping or {}, ['name']),
553
- ignore_keys(di_settings or {}, [
554
- 'catalog',
555
- 'config',
556
- 'data_integration_uuid',
557
- ]),
796
+ ignore_keys(
797
+ di_settings or {},
798
+ [
799
+ 'catalog',
800
+ 'config',
801
+ 'data_integration_uuid',
802
+ ],
803
+ ),
558
804
  )
559
805
  di_metadata['sql'] = uuid in SQL_SOURCES_MAPPING
560
806
 
@@ -572,9 +818,8 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
572
818
 
573
819
  @property
574
820
  def executable(self) -> bool:
575
- return (
576
- self.type not in NON_PIPELINE_EXECUTABLE_BLOCK_TYPES
577
- and (self.pipeline is None or self.pipeline.type != PipelineType.STREAMING)
821
+ return self.type not in NON_PIPELINE_EXECUTABLE_BLOCK_TYPES and (
822
+ self.pipeline is None or self.pipeline.type != PipelineType.STREAMING
578
823
  )
579
824
 
580
825
  @property
@@ -584,10 +829,15 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
584
829
  self._outputs = self.get_outputs()
585
830
  return self._outputs
586
831
 
587
- async def __outputs_async(self) -> List:
832
+ async def __outputs_async(
833
+ self, exclude_blank_variable_uuids: bool = False, max_results: Optional[int] = None
834
+ ) -> List:
588
835
  if not self._outputs_loaded:
589
836
  if self._outputs is None or len(self._outputs) == 0:
590
- self._outputs = await self.__get_outputs_async()
837
+ self._outputs = await self.__get_outputs_async(
838
+ exclude_blank_variable_uuids=exclude_blank_variable_uuids,
839
+ max_results=max_results,
840
+ )
591
841
  return self._outputs
592
842
 
593
843
  @property
@@ -622,15 +872,20 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
622
872
  block_uuid: str,
623
873
  block_type: BlockType,
624
874
  language: BlockLanguage,
875
+ relative_path: bool = False,
625
876
  ) -> str:
626
877
  file_extension = BLOCK_LANGUAGE_TO_FILE_EXTENSION[language]
627
878
  block_directory = f'{block_type}s' if block_type != BlockType.CUSTOM else block_type
628
879
 
629
- return os.path.join(
630
- repo_path or os.getcwd(),
880
+ parts = []
881
+ if not relative_path:
882
+ parts.append(repo_path or os.getcwd())
883
+ parts += [
631
884
  block_directory,
632
885
  f'{block_uuid}.{file_extension}',
633
- )
886
+ ]
887
+
888
+ return os.path.join(*parts)
634
889
 
635
890
  @property
636
891
  def file_path(self) -> str:
@@ -645,12 +900,59 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
645
900
  return add_absolute_path(file_path)
646
901
 
647
902
  return self.__build_file_path(
648
- self.repo_path or os.getcwd(),
903
+ self.repo_path,
649
904
  self.uuid,
650
905
  self.type,
651
906
  self.language,
652
907
  )
653
908
 
909
+ def build_file_path_directory(
910
+ self,
911
+ block_uuid: Optional[str] = None,
912
+ ) -> Tuple[Optional[str], Optional[str]]:
913
+ file_path = None
914
+ file_path_absolute = None
915
+
916
+ if self.replicated_block and self.replicated_block_object:
917
+ (
918
+ file_path_absolute,
919
+ file_path,
920
+ ) = self.replicated_block_object.build_file_path_directory(
921
+ block_uuid=block_uuid,
922
+ )
923
+
924
+ if not file_path:
925
+ file_path = self.get_file_path_from_source() or self.configuration.get('file_path')
926
+
927
+ if not file_path:
928
+ file_path = self.__build_file_path(
929
+ self.repo_path or os.getcwd(),
930
+ self.uuid,
931
+ self.type,
932
+ self.language,
933
+ relative_path=True,
934
+ )
935
+
936
+ if block_uuid:
937
+ old_file_path = Path(file_path)
938
+ old_file_extension = old_file_path.suffix
939
+ new_file_name = f'{block_uuid}{old_file_extension}'
940
+ file_path = str(old_file_path.with_name(new_file_name))
941
+
942
+ if file_path:
943
+ file_path_absolute = add_absolute_path(file_path)
944
+
945
+ if not file_path_absolute and block_uuid:
946
+ file_path_absolute = self.__build_file_path(
947
+ self.repo_path or os.getcwd(),
948
+ block_uuid,
949
+ self.type,
950
+ self.language,
951
+ relative_path=False,
952
+ )
953
+
954
+ return file_path_absolute, file_path
955
+
654
956
  @property
655
957
  def file(self) -> File:
656
958
  if self.replicated_block and self.replicated_block_object:
@@ -661,15 +963,29 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
661
963
  if file:
662
964
  return file
663
965
 
664
- return File.from_path(self.file_path)
966
+ repo_path = self.pipeline.repo_path if self.pipeline else None
967
+ new_file = File.from_path(self.file_path, repo_path=repo_path)
968
+
969
+ if not new_file.filename or not new_file.dir_path:
970
+ new_file = File.from_path(
971
+ self.__build_file_path(
972
+ new_file.repo_path,
973
+ self.uuid,
974
+ self.type,
975
+ self.language,
976
+ )
977
+ )
978
+
979
+ return new_file
665
980
 
666
981
  @property
667
982
  def table_name(self) -> str:
668
983
  if self.configuration and self.configuration.get('data_provider_table'):
669
984
  return self.configuration['data_provider_table']
670
985
 
671
- table_name = f'{self.pipeline.uuid}_{clean_name_orig(self.uuid)}_'\
672
- f'{self.pipeline.version_name}'
986
+ table_name = (
987
+ f'{self.pipeline_uuid}_{clean_name_orig(self.uuid)}_' f'{self.pipeline.version_name}'
988
+ )
673
989
 
674
990
  env = (self.global_vars or dict()).get('env')
675
991
  if env == ENV_DEV:
@@ -680,31 +996,15 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
680
996
  return table_name
681
997
 
682
998
  @property
683
- def full_table_name(self) -> str:
999
+ def full_table_name(self) -> Optional[str]:
684
1000
  from mage_ai.data_preparation.models.block.sql.utils.shared import (
685
- extract_create_statement_table_name,
686
- extract_insert_statement_table_names,
687
- extract_update_statement_table_names,
1001
+ extract_full_table_name,
688
1002
  )
689
1003
 
690
- if not self.content:
691
- return None
692
-
693
- table_name = extract_create_statement_table_name(self.content)
694
- if table_name:
695
- return table_name
696
-
697
- matches = extract_insert_statement_table_names(self.content)
698
- if len(matches) == 0:
699
- matches = extract_update_statement_table_names(self.content)
700
-
701
- if len(matches) == 0:
702
- return None
703
-
704
- return matches[len(matches) - 1]
1004
+ return extract_full_table_name(self.content)
705
1005
 
706
1006
  @classmethod
707
- def after_create(self, block: 'Block', **kwargs) -> None:
1007
+ def after_create(cls, block: 'Block', **kwargs) -> None:
708
1008
  widget = kwargs.get('widget')
709
1009
  pipeline = kwargs.get('pipeline')
710
1010
  if pipeline is not None:
@@ -766,28 +1066,30 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
766
1066
  # Don’t create a file if it’s from another project.
767
1067
 
768
1068
  file_path_from_source = (
769
- configuration and
770
- configuration.get('file_source') and
771
- (configuration.get('file_source') or {}).get('path')
1069
+ configuration
1070
+ and configuration.get('file_source')
1071
+ and (configuration.get('file_source') or {}).get('path')
1072
+ )
1073
+ file_is_from_another_project = file_path_from_source and from_another_project(
1074
+ file_path=file_path_from_source,
1075
+ other_file_path=pipeline.dir_path if pipeline else None,
772
1076
  )
773
- file_is_from_another_project = (
774
- file_path_from_source and
775
- from_another_project(
776
- file_path=file_path_from_source,
777
- other_file_path=pipeline.dir_path if pipeline else None,
1077
+ absolute_file_path = (
1078
+ add_root_repo_path_to_relative_path(
1079
+ file_path_from_source,
778
1080
  )
1081
+ if file_path_from_source
1082
+ else None
779
1083
  )
780
- absolute_file_path = add_root_repo_path_to_relative_path(
781
- file_path_from_source,
782
- ) if file_path_from_source else None
783
-
784
- if not file_is_from_another_project and \
785
- (not absolute_file_path or not os.path.exists(absolute_file_path)):
786
-
787
- if not replicated_block and \
788
- (BlockType.DBT != block_type or BlockLanguage.YAML == language) and \
789
- BlockType.GLOBAL_DATA_PRODUCT != block_type:
790
1084
 
1085
+ if not file_is_from_another_project and (
1086
+ not absolute_file_path or not os.path.exists(absolute_file_path)
1087
+ ):
1088
+ if (
1089
+ not replicated_block
1090
+ and (BlockType.DBT != block_type or BlockLanguage.YAML == language)
1091
+ and BlockType.GLOBAL_DATA_PRODUCT != block_type
1092
+ ):
791
1093
  block_directory = self.file_directory_name(block_type)
792
1094
  if absolute_file_path:
793
1095
  block_dir_path = os.path.dirname(absolute_file_path)
@@ -803,19 +1105,24 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
803
1105
  file_path = os.path.join(block_dir_path, f'{uuid}.{file_extension}')
804
1106
  if os.path.exists(file_path):
805
1107
  already_exists = True
806
- if (pipeline is not None and pipeline.has_block(
807
- uuid,
808
- block_type=block_type,
809
- extension_uuid=extension_uuid,
810
- )) or require_unique_name:
1108
+ if (
1109
+ pipeline is not None
1110
+ and pipeline.has_block(
1111
+ uuid,
1112
+ block_type=block_type,
1113
+ extension_uuid=extension_uuid,
1114
+ )
1115
+ ) or require_unique_name:
811
1116
  """
812
1117
  The BLOCK_EXISTS_ERROR constant is used on the frontend to identify when
813
1118
  a user is trying to create a new block with an existing block name, and
814
1119
  link them to the existing block file so the user can choose to add the
815
1120
  existing block to their pipeline.
816
1121
  """
817
- raise Exception(f'{BLOCK_EXISTS_ERROR} Block {uuid} already exists. \
818
- Please use a different name.')
1122
+ raise Exception(
1123
+ f'{BLOCK_EXISTS_ERROR} Block {uuid} already exists. \
1124
+ Please use a different name.'
1125
+ )
819
1126
  else:
820
1127
  load_template(
821
1128
  block_type,
@@ -830,8 +1137,9 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
830
1137
  if not configuration.get('file_source'):
831
1138
  configuration['file_source'] = {}
832
1139
  if not configuration['file_source'].get('path'):
1140
+ relative_path = str(Path(repo_path).relative_to(base_repo_path_directory_name()))
833
1141
  configuration['file_source']['path'] = self.__build_file_path(
834
- get_repo_path(absolute_path=False, root_project=False),
1142
+ relative_path,
835
1143
  uuid,
836
1144
  block_type,
837
1145
  language,
@@ -875,8 +1183,13 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
875
1183
  return f'{block_type}s' if block_type != BlockType.CUSTOM else block_type
876
1184
 
877
1185
  @classmethod
878
- def block_type_from_path(self, block_file_absolute_path: str) -> BlockType:
879
- file_path = str(block_file_absolute_path).replace(get_repo_path(), '')
1186
+ def block_type_from_path(
1187
+ self, block_file_absolute_path: str, repo_path: str = None
1188
+ ) -> BlockType:
1189
+ warn_for_repo_path(repo_path)
1190
+ if not repo_path:
1191
+ repo_path = get_repo_path()
1192
+ file_path = str(block_file_absolute_path).replace(repo_path, '')
880
1193
  if file_path.startswith(os.sep):
881
1194
  file_path = file_path[1:]
882
1195
 
@@ -981,9 +1294,13 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
981
1294
  )
982
1295
  # For block_type SCRATCHPAD and MARKDOWN, also delete the file if possible
983
1296
  if self.type in NON_PIPELINE_EXECUTABLE_BLOCK_TYPES:
984
- pipelines = Pipeline.get_pipelines_by_block(self, widget=widget)
1297
+ pipelines = Pipeline.get_pipelines_by_block(
1298
+ self,
1299
+ repo_path=self.pipeline.repo_path,
1300
+ widget=widget,
1301
+ )
985
1302
  pipelines = [
986
- pipeline for pipeline in pipelines if self.pipeline.uuid != pipeline.uuid
1303
+ pipeline for pipeline in pipelines if self.pipeline_uuid != pipeline.uuid
987
1304
  ]
988
1305
  if len(pipelines) == 0:
989
1306
  os.remove(self.file_path)
@@ -991,7 +1308,7 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
991
1308
 
992
1309
  # TODO (tommy dang): delete this block from all pipelines in all projects
993
1310
  # If pipeline is not specified, delete the block from all pipelines and delete the file.
994
- pipelines = Pipeline.get_pipelines_by_block(self, widget=widget)
1311
+ pipelines = Pipeline.get_pipelines_by_block(self, repo_path=self.repo_path, widget=widget)
995
1312
  if not force:
996
1313
  for p in pipelines:
997
1314
  if not p.block_deletable(self):
@@ -1017,7 +1334,7 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1017
1334
  logging_tags: Dict = None,
1018
1335
  execution_uuid: str = None,
1019
1336
  from_notebook: bool = False,
1020
- **kwargs
1337
+ **kwargs,
1021
1338
  ) -> Dict:
1022
1339
  """
1023
1340
  This method will execute the block and run the callback functions if they exist
@@ -1051,9 +1368,11 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1051
1368
  # Print result to block output
1052
1369
  if not result:
1053
1370
  conditional_message += 'This block would not be executed in a trigger run.\n'
1054
- conditional_json = json.dumps(dict(
1055
- message=conditional_message,
1056
- ))
1371
+ conditional_json = json.dumps(
1372
+ dict(
1373
+ message=conditional_message,
1374
+ )
1375
+ )
1057
1376
  print(f'[__internal_test__]{conditional_json}')
1058
1377
 
1059
1378
  callback_arr = []
@@ -1068,7 +1387,7 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1068
1387
  logger=logger,
1069
1388
  logging_tags=logging_tags,
1070
1389
  from_notebook=from_notebook,
1071
- **kwargs
1390
+ **kwargs,
1072
1391
  )
1073
1392
  except Exception as error:
1074
1393
  for callback_block in callback_arr:
@@ -1125,143 +1444,232 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1125
1444
  override_outputs: bool = True,
1126
1445
  **kwargs,
1127
1446
  ) -> Dict:
1128
- if logging_tags is None:
1129
- logging_tags = dict()
1447
+ def __execute(
1448
+ self=self,
1449
+ analyze_outputs=analyze_outputs,
1450
+ block_run_outputs_cache=block_run_outputs_cache,
1451
+ build_block_output_stdout=build_block_output_stdout,
1452
+ custom_code=custom_code,
1453
+ data_integration_runtime_settings=data_integration_runtime_settings,
1454
+ disable_json_serialization=disable_json_serialization,
1455
+ dynamic_block_index=dynamic_block_index,
1456
+ dynamic_block_indexes=dynamic_block_indexes,
1457
+ dynamic_block_uuid=dynamic_block_uuid,
1458
+ dynamic_upstream_block_uuids=dynamic_upstream_block_uuids,
1459
+ execution_partition=execution_partition,
1460
+ execution_partition_previous=execution_partition_previous,
1461
+ from_notebook=from_notebook,
1462
+ global_vars=global_vars,
1463
+ input_from_output=input_from_output,
1464
+ kwargs=kwargs,
1465
+ logger=logger,
1466
+ logging_tags=logging_tags,
1467
+ metadata=metadata,
1468
+ output_messages_to_logs=output_messages_to_logs,
1469
+ override_outputs=override_outputs,
1470
+ run_all_blocks=run_all_blocks,
1471
+ run_settings=run_settings,
1472
+ runtime_arguments=runtime_arguments,
1473
+ store_variables=store_variables,
1474
+ update_status=update_status,
1475
+ verify_output=verify_output,
1476
+ ) -> Dict:
1477
+ if logging_tags is None:
1478
+ logging_tags = dict()
1130
1479
 
1131
- try:
1132
- if not run_all_blocks:
1133
- not_executed_upstream_blocks = list(
1134
- filter(lambda b: b.status == BlockStatus.NOT_EXECUTED, self.upstream_blocks)
1135
- )
1136
- all_upstream_is_dbt = all([BlockType.DBT == b.type
1137
- for b in not_executed_upstream_blocks])
1138
- if not all_upstream_is_dbt and len(not_executed_upstream_blocks) > 0:
1139
- upstream_block_uuids = list(map(lambda b: b.uuid, not_executed_upstream_blocks))
1140
- raise Exception(
1141
- f"Block {self.uuid}'s upstream blocks have not been executed yet. "
1142
- f'Please run upstream blocks {upstream_block_uuids} '
1143
- 'before running the current block.'
1480
+ try:
1481
+ if not run_all_blocks:
1482
+ not_executed_upstream_blocks = list(
1483
+ filter(
1484
+ lambda b: b.status == BlockStatus.NOT_EXECUTED,
1485
+ self.upstream_blocks,
1486
+ )
1144
1487
  )
1145
- global_vars = self.enrich_global_vars(
1146
- global_vars,
1147
- dynamic_block_index=dynamic_block_index,
1148
- )
1149
-
1150
- if output_messages_to_logs and not logger:
1151
- from mage_ai.data_preparation.models.block.constants import (
1152
- LOG_PARTITION_EDIT_PIPELINE,
1488
+ all_upstream_is_dbt = all([
1489
+ BlockType.DBT == b.type for b in not_executed_upstream_blocks
1490
+ ])
1491
+ if not all_upstream_is_dbt and len(not_executed_upstream_blocks) > 0:
1492
+ upstream_block_uuids = list(
1493
+ map(lambda b: b.uuid, not_executed_upstream_blocks)
1494
+ )
1495
+ raise Exception(
1496
+ f"Block {self.uuid}'s upstream blocks have not been executed yet. "
1497
+ f'Please run upstream blocks {upstream_block_uuids} '
1498
+ 'before running the current block.'
1499
+ )
1500
+ global_vars = self.enrich_global_vars(
1501
+ global_vars,
1502
+ dynamic_block_index=dynamic_block_index,
1153
1503
  )
1154
1504
 
1155
- logger_manager = LoggerManagerFactory.get_logger_manager(
1156
- block_uuid=datetime.utcnow().strftime(format='%Y%m%dT%H%M%S'),
1157
- partition=LOG_PARTITION_EDIT_PIPELINE,
1158
- pipeline_uuid=self.pipeline.uuid if self.pipeline else None,
1159
- subpartition=clean_name(self.uuid),
1160
- )
1161
- logger = DictLogger(logger_manager.logger)
1162
- logging_tags = dict(
1163
- block_type=self.type,
1164
- block_uuid=self.uuid,
1165
- pipeline_uuid=self.pipeline.uuid if self.pipeline else None,
1166
- )
1505
+ def __store_variables(
1506
+ variable_mapping: Dict[str, Any],
1507
+ skip_delete: Optional[bool] = None,
1508
+ save_variable_types_only: Optional[bool] = None,
1509
+ clean_variable_uuid: Optional[bool] = None,
1510
+ dynamic_block_index=dynamic_block_index,
1511
+ dynamic_block_uuid=dynamic_block_uuid,
1512
+ execution_partition=execution_partition,
1513
+ global_vars=global_vars,
1514
+ override_outputs=override_outputs,
1515
+ self=self,
1516
+ ) -> Optional[List[Variable]]:
1517
+ return self.store_variables(
1518
+ variable_mapping,
1519
+ clean_variable_uuid=clean_variable_uuid,
1520
+ execution_partition=execution_partition,
1521
+ override_outputs=override_outputs,
1522
+ skip_delete=skip_delete,
1523
+ spark=self.__get_spark_session_from_global_vars(
1524
+ global_vars=global_vars,
1525
+ ),
1526
+ dynamic_block_index=dynamic_block_index,
1527
+ dynamic_block_uuid=dynamic_block_uuid,
1528
+ save_variable_types_only=save_variable_types_only,
1529
+ )
1167
1530
 
1168
- output = self.execute_block(
1169
- block_run_outputs_cache=block_run_outputs_cache,
1170
- build_block_output_stdout=build_block_output_stdout,
1171
- custom_code=custom_code,
1172
- execution_partition=execution_partition,
1173
- from_notebook=from_notebook,
1174
- global_vars=global_vars,
1175
- logger=logger,
1176
- logging_tags=logging_tags,
1177
- input_from_output=input_from_output,
1178
- runtime_arguments=runtime_arguments,
1179
- dynamic_block_index=dynamic_block_index,
1180
- dynamic_block_indexes=dynamic_block_indexes,
1181
- dynamic_upstream_block_uuids=dynamic_upstream_block_uuids,
1182
- run_settings=run_settings,
1183
- data_integration_runtime_settings=data_integration_runtime_settings,
1184
- execution_partition_previous=execution_partition_previous,
1185
- metadata=metadata,
1186
- **kwargs,
1187
- )
1531
+ self._store_variables_in_block_function = __store_variables
1188
1532
 
1189
- if self.configuration and self.configuration.get('disable_query_preprocessing'):
1190
- output = dict(output=None)
1191
- else:
1192
- block_output = self.post_process_output(output)
1193
- variable_mapping = dict()
1194
-
1195
- if BlockType.CHART == self.type:
1196
- variable_mapping = block_output
1197
- output = dict(
1198
- output=simplejson.dumps(
1199
- block_output,
1200
- default=encode_complex,
1201
- ignore_nan=True,
1202
- ) if not disable_json_serialization else block_output,
1533
+ if output_messages_to_logs and not logger:
1534
+ from mage_ai.data_preparation.models.block.constants import (
1535
+ LOG_PARTITION_EDIT_PIPELINE,
1203
1536
  )
1204
- else:
1205
- output_count = len(block_output)
1206
- variable_keys = [f'output_{idx}' for idx in range(output_count)]
1207
- variable_mapping = dict(zip(variable_keys, block_output))
1208
1537
 
1209
- if store_variables and \
1210
- self.pipeline and \
1211
- self.pipeline.type != PipelineType.INTEGRATION:
1538
+ logger_manager = LoggerManagerFactory.get_logger_manager(
1539
+ block_uuid=datetime.utcnow().strftime(format='%Y%m%dT%H%M%S'),
1540
+ partition=LOG_PARTITION_EDIT_PIPELINE,
1541
+ pipeline_uuid=self.pipeline_uuid,
1542
+ subpartition=clean_name(self.uuid),
1543
+ )
1544
+ logger = DictLogger(logger_manager.logger)
1545
+ logging_tags = dict(
1546
+ block_type=self.type,
1547
+ block_uuid=self.uuid,
1548
+ pipeline_uuid=self.pipeline_uuid,
1549
+ )
1212
1550
 
1213
- try:
1214
- DX_PRINTER.critical(
1215
- block=self,
1216
- execution_partition=execution_partition,
1217
- override_outputs=override_outputs,
1218
- dynamic_block_uuid=dynamic_block_uuid,
1219
- __uuid='store_variables',
1220
- )
1551
+ output = self.execute_block(
1552
+ block_run_outputs_cache=block_run_outputs_cache,
1553
+ build_block_output_stdout=build_block_output_stdout,
1554
+ custom_code=custom_code,
1555
+ execution_partition=execution_partition,
1556
+ from_notebook=from_notebook,
1557
+ global_vars=global_vars,
1558
+ logger=logger,
1559
+ logging_tags=logging_tags,
1560
+ input_from_output=input_from_output,
1561
+ runtime_arguments=runtime_arguments,
1562
+ dynamic_block_index=dynamic_block_index,
1563
+ dynamic_block_indexes=dynamic_block_indexes,
1564
+ dynamic_upstream_block_uuids=dynamic_upstream_block_uuids,
1565
+ run_settings=run_settings,
1566
+ data_integration_runtime_settings=data_integration_runtime_settings,
1567
+ execution_partition_previous=execution_partition_previous,
1568
+ metadata=metadata,
1569
+ override_outputs=override_outputs,
1570
+ **kwargs,
1571
+ )
1221
1572
 
1222
- self.store_variables(
1223
- variable_mapping,
1224
- execution_partition=execution_partition,
1225
- override_outputs=override_outputs,
1226
- spark=self.__get_spark_session_from_global_vars(
1227
- global_vars=global_vars,
1573
+ if self.configuration and self.configuration.get('disable_query_preprocessing'):
1574
+ output = dict(output=None)
1575
+ else:
1576
+ block_output = self.post_process_output(output)
1577
+ variable_mapping = dict()
1578
+
1579
+ if BlockType.CHART == self.type:
1580
+ variable_mapping = block_output
1581
+ output = dict(
1582
+ output=(
1583
+ simplejson.dumps(
1584
+ block_output,
1585
+ default=encode_complex,
1586
+ ignore_nan=True,
1587
+ )
1588
+ if not disable_json_serialization
1589
+ else block_output
1228
1590
  ),
1229
- dynamic_block_index=dynamic_block_index,
1230
- dynamic_block_uuid=dynamic_block_uuid,
1231
- )
1232
- except ValueError as e:
1233
- if str(e) == 'Circular reference detected':
1234
- raise ValueError(
1235
- 'Please provide dataframe or json serializable data as output.'
1236
- )
1237
- raise e
1238
- # Reset outputs cache
1239
- self._outputs = None
1240
-
1241
- if BlockType.CHART != self.type:
1242
- if analyze_outputs:
1243
- self.analyze_outputs(
1244
- variable_mapping,
1245
- execution_partition=execution_partition,
1246
1591
  )
1247
1592
  else:
1248
- self.analyze_outputs(
1249
- variable_mapping,
1250
- execution_partition=execution_partition,
1251
- shape_only=True,
1252
- )
1593
+ output_count = len(block_output)
1594
+ variable_keys = [f'output_{idx}' for idx in range(output_count)]
1595
+ variable_mapping = dict(zip(variable_keys, block_output))
1596
+
1597
+ if (
1598
+ store_variables
1599
+ and self.pipeline
1600
+ and self.pipeline.type != PipelineType.INTEGRATION
1601
+ ):
1602
+ try:
1603
+ DX_PRINTER.critical(
1604
+ block=self,
1605
+ execution_partition=execution_partition,
1606
+ override_outputs=override_outputs,
1607
+ dynamic_block_uuid=dynamic_block_uuid,
1608
+ __uuid='store_variables',
1609
+ )
1253
1610
 
1254
- if update_status:
1255
- self.status = BlockStatus.EXECUTED
1256
- except Exception as err:
1257
- if update_status:
1258
- self.status = BlockStatus.FAILED
1259
- raise err
1260
- finally:
1261
- if update_status:
1262
- self.__update_pipeline_block(widget=BlockType.CHART == self.type)
1611
+ if self._store_variables_in_block_function and isinstance(
1612
+ variable_mapping, dict
1613
+ ):
1614
+ self._store_variables_in_block_function(variable_mapping)
1615
+
1616
+ except ValueError as e:
1617
+ if str(e) == 'Circular reference detected':
1618
+ raise ValueError(
1619
+ 'Please provide dataframe or json serializable data as output.'
1620
+ )
1621
+ raise e
1622
+
1623
+ if not is_dynamic_block_child(self):
1624
+ # This will be handled in the execute_custom_code file so that it’s only
1625
+ # invoked once.
1626
+ self.aggregate_summary_info()
1627
+
1628
+ # Reset outputs cache
1629
+ self._outputs = None
1630
+
1631
+ if BlockType.CHART != self.type:
1632
+ if analyze_outputs:
1633
+ self.analyze_outputs(
1634
+ variable_mapping,
1635
+ execution_partition=execution_partition,
1636
+ )
1637
+ else:
1638
+ self.analyze_outputs(
1639
+ variable_mapping,
1640
+ execution_partition=execution_partition,
1641
+ shape_only=True,
1642
+ )
1263
1643
 
1264
- return output
1644
+ if update_status:
1645
+ self.status = BlockStatus.EXECUTED
1646
+ except Exception as err:
1647
+ if update_status:
1648
+ self.status = BlockStatus.FAILED
1649
+ raise err
1650
+ finally:
1651
+ if update_status:
1652
+ self.__update_pipeline_block(widget=BlockType.CHART == self.type)
1653
+
1654
+ return output
1655
+
1656
+ # if MEMORY_MANAGER_V2:
1657
+ # metadata = {}
1658
+ # if execution_partition:
1659
+ # metadata['execution_partition'] = execution_partition
1660
+ # if from_notebook:
1661
+ # metadata['origin'] = 'ide'
1662
+ # with MemoryManager(
1663
+ # scope_uuid=os.path.join(
1664
+ # *([PIPELINES_FOLDER, self.pipeline_uuid] if self.pipeline else ['']),
1665
+ # self.uuid,
1666
+ # ),
1667
+ # process_uuid='block.execute_sync',
1668
+ # repo_path=self.repo_path,
1669
+ # metadata=metadata,
1670
+ # ):
1671
+ # return __execute()
1672
+ return __execute()
1265
1673
 
1266
1674
  def post_process_output(self, output: Dict) -> List:
1267
1675
  return output['output'] or []
@@ -1290,7 +1698,7 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1290
1698
  global_vars=global_vars,
1291
1699
  run_all_blocks=run_all_blocks,
1292
1700
  update_status=update_status,
1293
- )
1701
+ ),
1294
1702
  )
1295
1703
  else:
1296
1704
  self.execute_sync(
@@ -1335,19 +1743,19 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1335
1743
  if num_args > num_inputs:
1336
1744
  if num_upstream < num_args:
1337
1745
  raise Exception(
1338
- f'Block {self.uuid} may be missing upstream dependencies. '
1746
+ f"Block {self.uuid} may be missing upstream dependencies. "
1339
1747
  f'It expected to have {"at least " if has_var_args else ""}{num_args} '
1340
- f'arguments, but only received {num_inputs}. '
1341
- f'Confirm that the @{self.type} method declaration has the correct number '
1342
- 'of arguments.'
1748
+ f"arguments, but only received {num_inputs}. "
1749
+ f"Confirm that the @{self.type} method declaration has the correct number "
1750
+ "of arguments."
1343
1751
  )
1344
1752
  else:
1345
1753
  raise Exception(
1346
- f'Block {self.uuid} is missing input arguments. '
1754
+ f"Block {self.uuid} is missing input arguments. "
1347
1755
  f'It expected to have {"at least " if has_var_args else ""}{num_args} '
1348
- f'arguments, but only received {num_inputs}. '
1349
- f'Double check the @{self.type} method declaration has the correct number '
1350
- 'of arguments and that the upstream blocks have been executed.'
1756
+ f"arguments, but only received {num_inputs}. "
1757
+ f"Double check the @{self.type} method declaration has the correct number "
1758
+ "of arguments and that the upstream blocks have been executed."
1351
1759
  )
1352
1760
  elif num_args < num_inputs and not has_var_args:
1353
1761
  if num_upstream > num_args:
@@ -1379,21 +1787,26 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1379
1787
  input_args: List = None,
1380
1788
  metadata: Dict = None,
1381
1789
  ) -> Tuple[Dict, List, Dict, List[str]]:
1382
- # Only fetch the input variables that the destination block explicitly declares.
1383
- # If all the input variables are fetched, there is a chance that a lot of data from
1384
- # an upstream source block is loaded just to be used as inputs for the block’s
1385
- # decorated functions. Only do this for the notebook because
1790
+ """
1791
+ Only fetch the input variables that the destination block explicitly declares.
1792
+ If all the input variables are fetched, there is a chance that a lot of data from
1793
+ an upstream source block is loaded just to be used as inputs for the block’s
1794
+ decorated functions. Only do this for the notebook because
1795
+ """
1386
1796
  if from_notebook and self.is_data_integration():
1387
- input_vars, kwargs_vars, upstream_block_uuids = \
1388
- self.fetch_input_variables_and_catalog(
1389
- input_args,
1390
- execution_partition,
1391
- global_vars,
1392
- dynamic_block_index=dynamic_block_index,
1393
- dynamic_block_indexes=dynamic_block_indexes,
1394
- dynamic_upstream_block_uuids=dynamic_upstream_block_uuids,
1395
- from_notebook=from_notebook,
1396
- )
1797
+ (
1798
+ input_vars,
1799
+ kwargs_vars,
1800
+ upstream_block_uuids,
1801
+ ) = self.fetch_input_variables_and_catalog(
1802
+ input_args,
1803
+ execution_partition,
1804
+ global_vars,
1805
+ dynamic_block_index=dynamic_block_index,
1806
+ dynamic_block_indexes=dynamic_block_indexes,
1807
+ dynamic_upstream_block_uuids=dynamic_upstream_block_uuids,
1808
+ from_notebook=from_notebook,
1809
+ )
1397
1810
  else:
1398
1811
  input_vars, kwargs_vars, upstream_block_uuids = self.fetch_input_variables(
1399
1812
  input_args,
@@ -1452,18 +1865,22 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1452
1865
  logging_tags=logging_tags,
1453
1866
  ):
1454
1867
  # Fetch input variables
1455
- outputs_from_input_vars, input_vars, kwargs_vars, upstream_block_uuids = \
1456
- self.__get_outputs_from_input_vars(
1457
- block_run_outputs_cache=block_run_outputs_cache,
1458
- dynamic_block_index=dynamic_block_index,
1459
- dynamic_block_indexes=dynamic_block_indexes,
1460
- dynamic_upstream_block_uuids=dynamic_upstream_block_uuids,
1461
- execution_partition=execution_partition,
1462
- from_notebook=from_notebook,
1463
- global_vars=global_vars,
1464
- input_args=input_args,
1465
- metadata=metadata,
1466
- )
1868
+ (
1869
+ outputs_from_input_vars,
1870
+ input_vars,
1871
+ kwargs_vars,
1872
+ upstream_block_uuids,
1873
+ ) = self.__get_outputs_from_input_vars(
1874
+ block_run_outputs_cache=block_run_outputs_cache,
1875
+ dynamic_block_index=dynamic_block_index,
1876
+ dynamic_block_indexes=dynamic_block_indexes,
1877
+ dynamic_upstream_block_uuids=dynamic_upstream_block_uuids,
1878
+ execution_partition=execution_partition,
1879
+ from_notebook=from_notebook,
1880
+ global_vars=global_vars,
1881
+ input_args=input_args,
1882
+ metadata=metadata,
1883
+ )
1467
1884
 
1468
1885
  global_vars_copy = global_vars.copy()
1469
1886
  for kwargs_var in kwargs_vars:
@@ -1589,11 +2006,14 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1589
2006
  preprocesser_functions = []
1590
2007
  test_functions = []
1591
2008
 
1592
- results = merge_dict({
1593
- 'preprocesser': self._block_decorator(preprocesser_functions),
1594
- 'test': self._block_decorator(test_functions),
1595
- self.type: self._block_decorator(decorated_functions),
1596
- }, outputs_from_input_vars)
2009
+ results = merge_dict(
2010
+ {
2011
+ 'preprocesser': self._block_decorator(preprocesser_functions),
2012
+ 'test': self._block_decorator(test_functions),
2013
+ self.type: self._block_decorator(decorated_functions),
2014
+ },
2015
+ outputs_from_input_vars,
2016
+ )
1597
2017
 
1598
2018
  if custom_code is not None and custom_code.strip():
1599
2019
  if BlockType.CHART != self.type:
@@ -1611,8 +2031,12 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1611
2031
  self.execute_block_function(
1612
2032
  preprocesser_function,
1613
2033
  input_vars,
2034
+ dynamic_block_index=dynamic_block_index,
2035
+ execution_partition=execution_partition,
1614
2036
  from_notebook=from_notebook,
1615
2037
  global_vars=global_vars,
2038
+ logger=logger,
2039
+ logging_tags=logging_tags,
1616
2040
  )
1617
2041
 
1618
2042
  block_function = self._validate_execution(decorated_functions, input_vars)
@@ -1630,8 +2054,12 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1630
2054
  outputs = self.execute_block_function(
1631
2055
  block_function,
1632
2056
  input_vars,
2057
+ dynamic_block_index=dynamic_block_index,
2058
+ execution_partition=execution_partition,
1633
2059
  from_notebook=from_notebook,
1634
2060
  global_vars=global_vars,
2061
+ logger=logger,
2062
+ logging_tags=logging_tags,
1635
2063
  )
1636
2064
 
1637
2065
  if track_spark:
@@ -1641,7 +2069,11 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1641
2069
 
1642
2070
  if outputs is None:
1643
2071
  outputs = []
1644
- if type(outputs) is not list:
2072
+
2073
+ if isinstance(outputs, tuple):
2074
+ outputs = list(outputs)
2075
+
2076
+ if not isinstance(outputs, list):
1645
2077
  outputs = [outputs]
1646
2078
 
1647
2079
  return outputs
@@ -1650,26 +2082,148 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1650
2082
  self,
1651
2083
  block_function: Callable,
1652
2084
  input_vars: List,
2085
+ dynamic_block_index: Optional[int] = None,
2086
+ dynamic_block_uuid: Optional[str] = None,
2087
+ execution_partition: Optional[str] = None,
1653
2088
  from_notebook: bool = False,
1654
- global_vars: Dict = None,
2089
+ global_vars: Optional[Dict] = None,
1655
2090
  initialize_decorator_modules: bool = True,
1656
- ) -> Dict:
1657
- block_function_updated = block_function
2091
+ logger: Optional[Logger] = None,
2092
+ logging_tags: Optional[Dict] = None,
2093
+ ) -> List[Dict[str, Any]]:
2094
+ from mage_ai.settings.server import (
2095
+ MEMORY_MANAGER_V2, # Need here to mock in tests
2096
+ )
2097
+
2098
+ sig = signature(block_function)
2099
+ has_kwargs = any([p.kind == p.VAR_KEYWORD for p in sig.parameters.values()])
1658
2100
 
2101
+ block_function_updated = block_function
1659
2102
  if from_notebook and initialize_decorator_modules:
1660
2103
  block_function_updated = self.__initialize_decorator_modules(
1661
2104
  block_function,
1662
- [self.type],
2105
+ [str(self.type.value) if not isinstance(self.type, str) else str(self.type)]
2106
+ if self.type
2107
+ else [],
1663
2108
  )
1664
2109
 
1665
- sig = signature(block_function)
1666
- has_kwargs = any([p.kind == p.VAR_KEYWORD for p in sig.parameters.values()])
1667
-
1668
- if has_kwargs and global_vars is not None and len(global_vars) != 0:
2110
+ write_policy = (
2111
+ self.write_settings.batch_settings.mode
2112
+ if self.write_settings and self.write_settings.batch_settings
2113
+ else None
2114
+ )
2115
+ append_data = ExportWritePolicy.APPEND == write_policy
2116
+ part_index = None
2117
+ if append_data:
2118
+ block_uuid, changed = uuid_for_output_variables(
2119
+ self,
2120
+ block_uuid=self.uuid,
2121
+ dynamic_block_index=dynamic_block_index,
2122
+ )
2123
+ variable_object = self.get_variable_object(
2124
+ block_uuid=block_uuid,
2125
+ partition=execution_partition,
2126
+ )
2127
+ part_uuids = variable_object.part_uuids
2128
+ if part_uuids is not None:
2129
+ part_index = len(part_uuids)
2130
+ if global_vars:
2131
+ global_vars.update(part_index=part_index)
2132
+
2133
+ if MEMORY_MANAGER_V2:
2134
+ log_message_prefix = self.uuid
2135
+ if self.pipeline:
2136
+ log_message_prefix = f'{self.pipeline_uuid}:{log_message_prefix}'
2137
+ log_message_prefix = f'[{log_message_prefix}:execute_block_function]'
2138
+
2139
+ output, self.resource_usage = execute_with_memory_tracking(
2140
+ block_function_updated,
2141
+ args=input_vars,
2142
+ kwargs=global_vars
2143
+ if has_kwargs and global_vars is not None and len(global_vars) != 0
2144
+ else None,
2145
+ logger=logger,
2146
+ logging_tags=logging_tags,
2147
+ log_message_prefix=log_message_prefix,
2148
+ )
2149
+ elif has_kwargs and global_vars is not None and len(global_vars) != 0:
1669
2150
  output = block_function_updated(*input_vars, **global_vars)
1670
2151
  else:
1671
2152
  output = block_function_updated(*input_vars)
1672
2153
 
2154
+ if MEMORY_MANAGER_V2 and inspect.isgeneratorfunction(block_function_updated):
2155
+ variable_types = []
2156
+ dynamic_child = is_dynamic_block_child(self)
2157
+ output_count = part_index if part_index is not None else 0
2158
+ if output is not None and is_iterable(output):
2159
+ if dynamic_child or self.is_dynamic_child:
2160
+ # Each child will delete its own data
2161
+ # How do we delete everything ahead of time?
2162
+ delete_variable_objects_for_dynamic_child(
2163
+ self,
2164
+ dynamic_block_index=dynamic_block_index,
2165
+ execution_partition=execution_partition,
2166
+ )
2167
+ else:
2168
+ self.delete_variables(
2169
+ dynamic_block_index=dynamic_block_index,
2170
+ dynamic_block_uuid=dynamic_block_uuid,
2171
+ execution_partition=execution_partition,
2172
+ )
2173
+
2174
+ for data in output:
2175
+ if self._store_variables_in_block_function is None:
2176
+ raise Exception(
2177
+ 'Store variables function isn’t defined, '
2178
+ 'don’t proceed or else no data will be persisted'
2179
+ )
2180
+
2181
+ store_options = {}
2182
+ if output_count >= 1:
2183
+ store_options['override_outputs'] = False
2184
+
2185
+ variable_mapping = {}
2186
+
2187
+ def __output_key(order: int, output_count=output_count):
2188
+ return os.path.join(f'output_{order}', str(output_count))
2189
+
2190
+ if is_basic_iterable(data):
2191
+ if data is None or len(data) == 1:
2192
+ variable_mapping[__output_key(0)] = data
2193
+ elif len(data) == 2 and isinstance(data[1], dict):
2194
+ variable_mapping[__output_key(0)] = data[0]
2195
+ variable_mapping[__output_key(1)] = data[1]
2196
+ else:
2197
+ for idx, item in enumerate(data):
2198
+ variable_mapping[__output_key(idx)] = item
2199
+ else:
2200
+ variable_mapping[__output_key(0)] = data
2201
+
2202
+ variables = self._store_variables_in_block_function(
2203
+ variable_mapping=variable_mapping,
2204
+ clean_variable_uuid=False,
2205
+ skip_delete=True,
2206
+ **store_options,
2207
+ )
2208
+ if variables is not None and isinstance(variables, list):
2209
+ variable_types += [
2210
+ variable.variable_type
2211
+ for variable in variables
2212
+ if isinstance(variable, Variable)
2213
+ ]
2214
+
2215
+ output_count += 1
2216
+
2217
+ if len(variable_types) >= 1 and self._store_variables_in_block_function:
2218
+ self._store_variables_in_block_function(
2219
+ {'output_0': variable_types},
2220
+ save_variable_types_only=True,
2221
+ )
2222
+
2223
+ self._store_variables_in_block_function = None
2224
+
2225
+ if output is None:
2226
+ return []
1673
2227
  return output
1674
2228
 
1675
2229
  def __initialize_decorator_modules(
@@ -1748,11 +2302,18 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1748
2302
  upstream block UUIDs.
1749
2303
  """
1750
2304
 
1751
- if any([is_dynamic_block(
1752
- upstream_block,
1753
- ) or is_dynamic_block_child(
1754
- upstream_block,
1755
- ) for upstream_block in self.upstream_blocks]):
2305
+ if (self.is_dynamic_v2 and self.is_dynamic_child) or (
2306
+ not self.is_dynamic_v2
2307
+ and any([
2308
+ is_dynamic_block(
2309
+ upstream_block,
2310
+ )
2311
+ or is_dynamic_block_child(
2312
+ upstream_block,
2313
+ )
2314
+ for upstream_block in self.upstream_blocks
2315
+ ])
2316
+ ):
1756
2317
  return fetch_input_variables_for_dynamic_upstream_blocks(
1757
2318
  self,
1758
2319
  input_args,
@@ -1761,7 +2322,6 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1761
2322
  execution_partition=execution_partition,
1762
2323
  from_notebook=from_notebook,
1763
2324
  global_vars=global_vars,
1764
- # For non-dynamic upstream blocks
1765
2325
  block_run_outputs_cache=block_run_outputs_cache,
1766
2326
  data_integration_settings_mapping=data_integration_settings_mapping,
1767
2327
  upstream_block_uuids_override=upstream_block_uuids_override,
@@ -1796,8 +2356,8 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1796
2356
  for v in output_variable_objects:
1797
2357
  if v.variable_type != VariableType.DATAFRAME:
1798
2358
  continue
1799
- data = self.pipeline.variable_manager.get_variable(
1800
- self.pipeline.uuid,
2359
+ data = self.variable_manager.get_variable(
2360
+ self.pipeline_uuid,
1801
2361
  self.uuid,
1802
2362
  v.uuid,
1803
2363
  variable_type=VariableType.DATAFRAME_ANALYSIS,
@@ -1809,12 +2369,12 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1809
2369
  def get_variables_by_block(
1810
2370
  self,
1811
2371
  block_uuid: str,
1812
- dynamic_block_index: int = None,
1813
- dynamic_block_uuid: str = None,
1814
- partition: str = None,
1815
- ):
1816
- variable_manager = self.pipeline.variable_manager
1817
-
2372
+ clean_block_uuid: bool = True,
2373
+ dynamic_block_index: Optional[int] = None,
2374
+ dynamic_block_uuid: Optional[str] = None,
2375
+ max_results: Optional[int] = None,
2376
+ partition: Optional[str] = None,
2377
+ ) -> List[str]:
1818
2378
  block_uuid_use, changed = uuid_for_output_variables(
1819
2379
  self,
1820
2380
  block_uuid=block_uuid,
@@ -1822,10 +2382,11 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1822
2382
  dynamic_block_uuid=dynamic_block_uuid,
1823
2383
  )
1824
2384
 
1825
- res = variable_manager.get_variables_by_block(
1826
- self.pipeline.uuid,
2385
+ res = self.variable_manager.get_variables_by_block(
2386
+ self.pipeline_uuid,
1827
2387
  block_uuid=block_uuid_use,
1828
- clean_block_uuid=not changed,
2388
+ clean_block_uuid=not changed and clean_block_uuid,
2389
+ max_results=max_results,
1829
2390
  partition=partition,
1830
2391
  )
1831
2392
 
@@ -1849,9 +2410,12 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1849
2410
  partition: str = None,
1850
2411
  raise_exception: bool = False,
1851
2412
  spark=None,
2413
+ input_data_types: Optional[List[InputDataType]] = None,
2414
+ read_batch_settings: Optional[BatchSettings] = None,
2415
+ read_chunks: Optional[List[ChunkKeyTypeUnion]] = None,
2416
+ write_batch_settings: Optional[BatchSettings] = None,
2417
+ write_chunks: Optional[List[ChunkKeyTypeUnion]] = None,
1852
2418
  ):
1853
- variable_manager = self.pipeline.variable_manager
1854
-
1855
2419
  block_uuid_use, changed = uuid_for_output_variables(
1856
2420
  self,
1857
2421
  block_uuid=block_uuid,
@@ -1859,40 +2423,151 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1859
2423
  dynamic_block_uuid=dynamic_block_uuid,
1860
2424
  )
1861
2425
 
1862
- value = variable_manager.get_variable(
1863
- self.pipeline.uuid,
2426
+ value = self.variable_manager.get_variable(
2427
+ self.pipeline_uuid,
1864
2428
  block_uuid=block_uuid_use,
1865
2429
  clean_block_uuid=not changed,
1866
2430
  partition=partition,
1867
2431
  raise_exception=raise_exception,
1868
2432
  spark=spark,
1869
2433
  variable_uuid=variable_uuid,
2434
+ input_data_types=input_data_types,
2435
+ read_batch_settings=read_batch_settings,
2436
+ read_chunks=read_chunks,
2437
+ write_batch_settings=write_batch_settings,
2438
+ write_chunks=write_chunks,
1870
2439
  )
1871
2440
 
1872
2441
  return value
1873
2442
 
1874
- def get_variable_object(
2443
+ def read_partial_data(
1875
2444
  self,
1876
- block_uuid: str,
1877
- variable_uuid: str = None,
1878
- dynamic_block_index: int = None,
1879
- partition: str = None,
2445
+ variable_uuid: str,
2446
+ batch_settings: Optional[BatchSettings] = None,
2447
+ chunks: Optional[List[ChunkKeyTypeUnion]] = None,
2448
+ input_data_types: Optional[List[InputDataType]] = None,
2449
+ part_uuid: Optional[Union[int, str]] = None,
2450
+ partition: Optional[str] = None,
1880
2451
  ):
1881
- variable_manager = self.pipeline.variable_manager
2452
+ return self.get_variable_object(
2453
+ self.uuid,
2454
+ variable_uuid,
2455
+ partition=partition,
2456
+ ).read_partial_data(
2457
+ batch_settings=batch_settings,
2458
+ chunks=chunks,
2459
+ input_data_types=input_data_types,
2460
+ part_uuid=part_uuid,
2461
+ )
1882
2462
 
2463
+ def get_variable_object(
2464
+ self,
2465
+ block_uuid: Optional[str] = None,
2466
+ variable_uuid: Optional[str] = None,
2467
+ clean_block_uuid: bool = True,
2468
+ dynamic_block_index: Optional[int] = None,
2469
+ input_data_types: Optional[List[InputDataType]] = None,
2470
+ ordinal_position: Optional[int] = None, # Used to get cached variable information
2471
+ partition: Optional[str] = None,
2472
+ read_batch_settings: Optional[BatchSettings] = None,
2473
+ read_chunks: Optional[List[ChunkKeyTypeUnion]] = None,
2474
+ skip_check_variable_type: Optional[bool] = None,
2475
+ write_batch_settings: Optional[BatchSettings] = None,
2476
+ write_chunks: Optional[List[ChunkKeyTypeUnion]] = None,
2477
+ ) -> Variable:
2478
+ block_uuid = block_uuid or self.uuid
1883
2479
  block_uuid, changed = uuid_for_output_variables(
1884
2480
  self,
1885
2481
  block_uuid=block_uuid,
1886
2482
  dynamic_block_index=dynamic_block_index,
1887
2483
  )
1888
2484
 
1889
- return variable_manager.get_variable_object(
1890
- self.pipeline.uuid,
2485
+ variable_type_information = None
2486
+ variable_types_information = None
2487
+ skip_check_variable_type = skip_check_variable_type or False
2488
+ if VARIABLE_DATA_OUTPUT_META_CACHE:
2489
+ dynamic_child = is_dynamic_block_child(self)
2490
+ group_type = (
2491
+ VariableAggregateSummaryGroupType.DYNAMIC
2492
+ if dynamic_child
2493
+ else VariableAggregateSummaryGroupType.PARTS
2494
+ )
2495
+ variable_type_information = self.get_variable_aggregate_cache(
2496
+ variable_uuid, VariableAggregateDataType.TYPE, default_group_type=group_type
2497
+ )
2498
+ variable_types_information = self.get_variable_aggregate_cache(
2499
+ variable_uuid,
2500
+ VariableAggregateDataType.TYPE,
2501
+ group_type=group_type,
2502
+ partition=partition,
2503
+ )
2504
+
2505
+ if (
2506
+ variable_type_information
2507
+ and variable_type_information.type == VariableType.ITERABLE
2508
+ and not variable_types_information
2509
+ ):
2510
+ # If the dynamic parent block is an interable with no types information,
2511
+ # then the data from the parent block won’t have any type information.
2512
+ # Skip variable type check when instantiating a variable object for the children.
2513
+ skip_check_variable_type = True
2514
+ elif (
2515
+ dynamic_child
2516
+ and variable_types_information is not None
2517
+ and isinstance(variable_types_information, Iterable)
2518
+ and (
2519
+ (
2520
+ dynamic_block_index is not None
2521
+ and int(dynamic_block_index) < len(variable_types_information)
2522
+ )
2523
+ or (
2524
+ ordinal_position is not None
2525
+ and int(ordinal_position) < len(variable_types_information)
2526
+ )
2527
+ )
2528
+ ):
2529
+ position = (
2530
+ int(ordinal_position)
2531
+ if ordinal_position is not None
2532
+ else int(dynamic_block_index)
2533
+ if dynamic_block_index is not None
2534
+ else None
2535
+ )
2536
+ if position is not None and isinstance(variable_types_information, Iterable):
2537
+ variable_type_information = variable_types_information[position]
2538
+ if (
2539
+ isinstance(variable_type_information, Iterable)
2540
+ and len(variable_type_information) >= 1
2541
+ ):
2542
+ variable_type_information = variable_type_information[0]
2543
+
2544
+ variable_types_information = None
2545
+
2546
+ variable_types = []
2547
+ if isinstance(variable_types_information, Iterable):
2548
+ for v in variable_types_information:
2549
+ if isinstance(v, list):
2550
+ variable_types += [vv.type for vv in v]
2551
+ else:
2552
+ variable_types.append(v.type)
2553
+
2554
+ return self.variable_manager.get_variable_object(
2555
+ self.pipeline_uuid,
1891
2556
  block_uuid=block_uuid,
1892
- clean_block_uuid=not changed,
2557
+ clean_block_uuid=not changed and clean_block_uuid,
1893
2558
  partition=partition,
1894
2559
  spark=self.get_spark_session(),
2560
+ skip_check_variable_type=skip_check_variable_type,
2561
+ variable_type=variable_type_information.type
2562
+ if variable_type_information is not None
2563
+ else None,
2564
+ variable_types=variable_types,
1895
2565
  variable_uuid=variable_uuid,
2566
+ input_data_types=input_data_types,
2567
+ read_batch_settings=read_batch_settings,
2568
+ read_chunks=read_chunks,
2569
+ write_batch_settings=write_batch_settings,
2570
+ write_chunks=write_chunks,
1896
2571
  )
1897
2572
 
1898
2573
  def get_raw_outputs(
@@ -1903,6 +2578,11 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1903
2578
  global_vars: Dict = None,
1904
2579
  dynamic_block_index: int = None,
1905
2580
  dynamic_block_uuid: str = None,
2581
+ input_data_types: Optional[List[InputDataType]] = None,
2582
+ read_batch_settings: Optional[BatchSettings] = None,
2583
+ read_chunks: Optional[List[ChunkKeyTypeUnion]] = None,
2584
+ write_batch_settings: Optional[BatchSettings] = None,
2585
+ write_chunks: Optional[List[ChunkKeyTypeUnion]] = None,
1906
2586
  ) -> List[Any]:
1907
2587
  all_variables = self.get_variables_by_block(
1908
2588
  block_uuid=block_uuid,
@@ -1914,6 +2594,9 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1914
2594
  outputs = []
1915
2595
 
1916
2596
  for variable_uuid in all_variables:
2597
+ if not is_output_variable(variable_uuid):
2598
+ continue
2599
+
1917
2600
  variable = self.pipeline.get_block_variable(
1918
2601
  block_uuid,
1919
2602
  variable_uuid,
@@ -1924,6 +2607,11 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1924
2607
  spark=self.__get_spark_session_from_global_vars(global_vars),
1925
2608
  dynamic_block_index=dynamic_block_index,
1926
2609
  dynamic_block_uuid=dynamic_block_uuid,
2610
+ input_data_types=input_data_types,
2611
+ read_batch_settings=read_batch_settings,
2612
+ read_chunks=read_chunks,
2613
+ write_batch_settings=write_batch_settings,
2614
+ write_chunks=write_chunks,
1927
2615
  )
1928
2616
  outputs.append(variable)
1929
2617
 
@@ -1931,411 +2619,181 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
1931
2619
 
1932
2620
  def get_outputs(
1933
2621
  self,
1934
- execution_partition: str = None,
1935
- include_print_outputs: bool = True,
2622
+ block_uuid: Optional[str] = None,
1936
2623
  csv_lines_only: bool = False,
2624
+ dynamic_block_index: Optional[int] = None,
2625
+ exclude_blank_variable_uuids: bool = False,
2626
+ execution_partition: Optional[str] = None,
2627
+ include_print_outputs: bool = True,
2628
+ metadata: Optional[Dict] = None,
1937
2629
  sample: bool = True,
1938
- sample_count: int = DATAFRAME_SAMPLE_COUNT_PREVIEW,
1939
- variable_type: VariableType = None,
1940
- block_uuid: str = None,
1941
- selected_variables: List[str] = None,
1942
- metadata: Dict = None,
1943
- dynamic_block_index: int = None,
1944
- ) -> List[Dict]:
1945
- data_products = []
1946
- outputs = []
1947
-
2630
+ sample_count: Optional[int] = None,
2631
+ selected_variables: Optional[List[str]] = None,
2632
+ variable_type: Optional[VariableType] = None,
2633
+ ) -> List[Dict[str, Any]]:
1948
2634
  is_dynamic_child = is_dynamic_block_child(self)
1949
2635
  is_dynamic = is_dynamic_block(self)
1950
2636
 
1951
- if is_dynamic_child or is_dynamic:
1952
- pairs = []
1953
-
1954
- if is_dynamic_child:
1955
- lazy_variable_controller = get_outputs_for_dynamic_child(
1956
- self,
1957
- execution_partition=execution_partition,
1958
- sample=sample,
1959
- sample_count=sample_count,
1960
- )
1961
- pairs = lazy_variable_controller.render(
1962
- dynamic_block_index=dynamic_block_index,
1963
- )
1964
- elif is_dynamic:
1965
- tup = get_outputs_for_dynamic_block(
1966
- self,
1967
- execution_partition=execution_partition,
1968
- sample=sample,
1969
- sample_count=sample_count,
1970
- )
1971
- pairs.append(tup)
1972
-
1973
- for pair in pairs:
1974
- child_data = None
1975
- metadata = None
1976
- if len(pair) >= 1:
1977
- child_data = pair[0]
1978
- if len(pair) >= 2:
1979
- metadata = pair[1]
1980
-
1981
- for output, variable_uuid in [
1982
- (child_data, 'child_data'),
1983
- (metadata, 'metadata'),
1984
- ]:
1985
- if output is None:
1986
- continue
1987
-
1988
- data, is_data_product = self.__format_output_data(
1989
- output,
1990
- variable_uuid,
1991
- block_uuid=self.uuid,
1992
- csv_lines_only=csv_lines_only,
1993
- execution_partition=execution_partition,
1994
- )
1995
-
1996
- outputs_below_limit = not sample or not sample_count
1997
- if is_data_product:
1998
- outputs_below_limit = outputs_below_limit or \
1999
- (sample_count is not None and len(data_products) < sample_count)
2000
- else:
2001
- outputs_below_limit = outputs_below_limit or \
2002
- (sample_count is not None and len(outputs) < sample_count)
2003
-
2004
- if outputs_below_limit:
2005
- if is_data_product:
2006
- data_products.append(data)
2007
- else:
2008
- outputs.append(data)
2009
- else:
2010
- if self.pipeline is None:
2011
- return
2012
-
2013
- if not block_uuid:
2014
- block_uuid = self.uuid
2015
-
2016
- # The block_run’s block_uuid for replicated blocks will be in this format:
2017
- # [block_uuid]:[replicated_block_uuid]
2018
- # We need to use the original block_uuid to get the proper output.
2019
-
2020
- # Block runs for dynamic child blocks will have the following block UUID:
2021
- # [block.uuid]:[index]
2022
- # Don’t use the original UUID even if the block is a replica because it will get rid of
2023
- # the dynamic child block index.
2024
-
2025
- data_products = []
2026
- outputs = []
2027
-
2028
- all_variables = self.get_variables_by_block(
2637
+ if not is_dynamic and not is_dynamic_child:
2638
+ return get_outputs_for_display_sync(
2639
+ self,
2029
2640
  block_uuid=block_uuid,
2030
- partition=execution_partition,
2641
+ csv_lines_only=csv_lines_only,
2642
+ exclude_blank_variable_uuids=exclude_blank_variable_uuids,
2643
+ execution_partition=execution_partition,
2644
+ include_print_outputs=include_print_outputs,
2645
+ sample=sample,
2646
+ sample_count=sample_count or DATAFRAME_SAMPLE_COUNT_PREVIEW,
2647
+ selected_variables=selected_variables,
2648
+ variable_type=variable_type,
2031
2649
  )
2032
2650
 
2033
- if not include_print_outputs:
2034
- all_variables = self.output_variables(
2035
- execution_partition=execution_partition,
2036
- )
2037
-
2038
- for v in all_variables:
2039
- if selected_variables and v not in selected_variables:
2040
- continue
2041
-
2042
- variable_object = self.get_variable_object(
2043
- block_uuid=block_uuid,
2044
- partition=execution_partition,
2045
- variable_uuid=v,
2046
- )
2047
-
2048
- if variable_type is not None and variable_object.variable_type != variable_type:
2049
- continue
2050
-
2051
- data = variable_object.read_data(
2052
- sample=sample,
2053
- sample_count=sample_count,
2054
- spark=self.get_spark_session(),
2055
- )
2056
- data, is_data_product = self.__format_output_data(
2057
- data,
2058
- v,
2059
- block_uuid=block_uuid,
2060
- csv_lines_only=csv_lines_only,
2061
- execution_partition=execution_partition,
2062
- )
2063
- if is_data_product:
2064
- data_products.append(data)
2065
- else:
2066
- outputs.append(data)
2067
-
2068
- return outputs + data_products
2069
-
2070
- async def __get_outputs_async(
2071
- self,
2072
- csv_lines_only: bool = False,
2073
- execution_partition: str = None,
2074
- include_print_outputs: bool = True,
2075
- sample: bool = True,
2076
- sample_count: int = DATAFRAME_SAMPLE_COUNT_PREVIEW,
2077
- variable_type: VariableType = None,
2078
- block_uuid: str = None,
2079
- dynamic_block_index: int = None,
2080
- ) -> List[Dict]:
2081
- data_products = []
2082
- outputs = []
2083
-
2084
- is_dynamic_child = is_dynamic_block_child(self)
2085
- is_dynamic = is_dynamic_block(self)
2086
-
2087
- if is_dynamic_child or is_dynamic:
2088
- pairs = []
2651
+ sample_count_use = sample_count or DYNAMIC_CHILD_BLOCK_SAMPLE_COUNT_PREVIEW
2652
+ output_sets = []
2653
+ variable_sets = []
2089
2654
 
2090
- if is_dynamic_child:
2091
- lazy_variable_controller = get_outputs_for_dynamic_child(
2092
- self,
2093
- execution_partition=execution_partition,
2094
- sample=sample,
2095
- sample_count=sample_count,
2096
- )
2097
- pairs = await lazy_variable_controller.render_async(
2098
- dynamic_block_index=dynamic_block_index,
2099
- )
2100
- elif is_dynamic:
2101
- tup = await get_outputs_for_dynamic_block_async(
2102
- self,
2103
- execution_partition=execution_partition,
2104
- sample=sample,
2105
- sample_count=sample_count,
2106
- )
2107
- pairs.append(tup)
2108
-
2109
- if len(pairs) > 10:
2110
- # Limit the number of dynamic block children we display output for in the UI
2111
- pairs = pairs[:DATAFRAME_SAMPLE_COUNT_PREVIEW]
2112
- for pair in pairs:
2113
- child_data = None
2114
- metadata = None
2115
- if len(pair) >= 1:
2116
- child_data = pair[0]
2117
- if len(pair) >= 2:
2118
- metadata = pair[1]
2119
-
2120
- for output, variable_uuid in [
2121
- (child_data, 'child_data'),
2122
- (metadata, 'metadata'),
2123
- ]:
2124
- if output is None:
2125
- continue
2126
-
2127
- data, is_data_product = self.__format_output_data(
2128
- output,
2129
- variable_uuid,
2130
- block_uuid=self.uuid,
2131
- csv_lines_only=csv_lines_only,
2132
- execution_partition=execution_partition,
2133
- )
2134
-
2135
- if is_data_product:
2136
- data_products.append(data)
2137
- else:
2138
- outputs.append(data)
2139
- else:
2140
- if self.pipeline is None:
2141
- return
2142
-
2143
- if not block_uuid:
2144
- block_uuid = self.uuid
2145
-
2146
- variable_manager = self.pipeline.variable_manager
2147
-
2148
- all_variables = variable_manager.get_variables_by_block(
2149
- self.pipeline.uuid,
2150
- block_uuid,
2151
- partition=execution_partition,
2152
- max_results=DATAFRAME_SAMPLE_COUNT_PREVIEW,
2655
+ if is_dynamic_child:
2656
+ lazy_variable_controller = get_outputs_for_dynamic_child(
2657
+ self,
2658
+ execution_partition=execution_partition,
2659
+ sample=sample,
2660
+ sample_count=sample_count_use,
2153
2661
  )
2662
+ variable_sets: List[
2663
+ Union[
2664
+ Tuple[Optional[Any], Dict],
2665
+ List[LazyVariableSet],
2666
+ ],
2667
+ ] = lazy_variable_controller.render(
2668
+ dynamic_block_index=dynamic_block_index,
2669
+ lazy_load=True,
2670
+ )
2671
+ elif is_dynamic:
2672
+ output_pair: List[
2673
+ Optional[
2674
+ Union[
2675
+ Any,
2676
+ Dict,
2677
+ int,
2678
+ pd.DataFrame,
2679
+ str,
2680
+ ]
2681
+ ]
2682
+ ] = get_outputs_for_dynamic_block(
2683
+ self,
2684
+ execution_partition=execution_partition,
2685
+ sample=sample,
2686
+ sample_count=sample_count_use,
2687
+ )
2688
+ output_sets.append(output_pair)
2154
2689
 
2155
- if not include_print_outputs:
2156
- all_variables = self.output_variables(execution_partition=execution_partition)
2157
-
2158
- for v in all_variables:
2159
- variable_object = variable_manager.get_variable_object(
2160
- self.pipeline.uuid,
2161
- block_uuid,
2162
- v,
2163
- partition=execution_partition,
2164
- spark=self.get_spark_session(),
2165
- )
2166
-
2167
- if variable_type is not None and variable_object.variable_type != variable_type:
2168
- continue
2169
-
2170
- data = await variable_object.read_data_async(
2171
- sample=True,
2172
- sample_count=sample_count,
2173
- spark=self.get_spark_session(),
2174
- )
2175
- data, is_data_product = self.__format_output_data(
2176
- data,
2177
- v,
2178
- block_uuid=block_uuid,
2179
- csv_lines_only=csv_lines_only,
2180
- execution_partition=execution_partition,
2181
- )
2182
- if is_data_product:
2183
- data_products.append(data)
2184
- else:
2185
- outputs.append(data)
2690
+ output_sets = output_sets[:DATAFRAME_SAMPLE_COUNT_PREVIEW]
2691
+ variable_sets = variable_sets[:DATAFRAME_SAMPLE_COUNT_PREVIEW]
2692
+ child_data_sets = [lazy_variable_set.read_data() for lazy_variable_set in variable_sets]
2186
2693
 
2187
- return outputs + data_products
2694
+ return get_outputs_for_display_dynamic_block(
2695
+ self,
2696
+ output_sets,
2697
+ child_data_sets,
2698
+ block_uuid=block_uuid,
2699
+ csv_lines_only=csv_lines_only,
2700
+ dynamic_block_index=dynamic_block_index,
2701
+ exclude_blank_variable_uuids=exclude_blank_variable_uuids,
2702
+ execution_partition=execution_partition,
2703
+ metadata=metadata,
2704
+ sample=sample,
2705
+ sample_count=sample_count_use,
2706
+ )
2188
2707
 
2189
- def __format_output_data(
2708
+ async def __get_outputs_async(
2190
2709
  self,
2191
- data: Any,
2192
- variable_uuid: str,
2193
- block_uuid: str = None,
2710
+ execution_partition: Optional[str] = None,
2711
+ include_print_outputs: bool = True,
2194
2712
  csv_lines_only: bool = False,
2195
- execution_partition: str = None,
2196
- skip_dynamic_block: bool = False,
2197
- ) -> Tuple[Dict, bool]:
2198
- """
2199
- Takes variable data and formats it to return to the frontend.
2200
-
2201
- Returns:
2202
- Tuple[Dict, bool]: Tuple of the formatted data and is_data_product boolean. Data product
2203
- outputs and non data product outputs are handled slightly differently.
2204
- """
2205
- variable_manager = self.pipeline.variable_manager
2206
-
2713
+ sample: bool = True,
2714
+ sample_count: Optional[int] = None,
2715
+ variable_type: Optional[VariableType] = None,
2716
+ block_uuid: Optional[str] = None,
2717
+ selected_variables: Optional[List[str]] = None,
2718
+ metadata: Optional[Dict] = None,
2719
+ dynamic_block_index: Optional[int] = None,
2720
+ exclude_blank_variable_uuids: bool = False,
2721
+ max_results: Optional[int] = None,
2722
+ ) -> List[Dict[str, Any]]:
2207
2723
  is_dynamic_child = is_dynamic_block_child(self)
2208
2724
  is_dynamic = is_dynamic_block(self)
2209
2725
 
2210
- if (is_dynamic_child or is_dynamic) and not skip_dynamic_block:
2211
- from mage_ai.data_preparation.models.block.dynamic.utils import (
2212
- coerce_into_dataframe,
2726
+ if not is_dynamic and not is_dynamic_child:
2727
+ return await get_outputs_for_display_async(
2728
+ self,
2729
+ block_uuid=block_uuid,
2730
+ csv_lines_only=csv_lines_only,
2731
+ exclude_blank_variable_uuids=exclude_blank_variable_uuids,
2732
+ execution_partition=execution_partition,
2733
+ include_print_outputs=include_print_outputs,
2734
+ sample=sample,
2735
+ sample_count=sample_count or DATAFRAME_SAMPLE_COUNT_PREVIEW,
2736
+ selected_variables=selected_variables,
2737
+ variable_type=variable_type,
2738
+ max_results=max_results,
2213
2739
  )
2214
2740
 
2215
- data, is_data_product = self.__format_output_data(
2216
- coerce_into_dataframe(data),
2217
- variable_uuid=variable_uuid,
2218
- skip_dynamic_block=True,
2741
+ sample_count_use = sample_count or DYNAMIC_CHILD_BLOCK_SAMPLE_COUNT_PREVIEW
2742
+ output_sets = []
2743
+ variable_sets = []
2744
+
2745
+ if is_dynamic_child:
2746
+ lazy_variable_controller = get_outputs_for_dynamic_child(
2747
+ self,
2748
+ execution_partition=execution_partition,
2749
+ limit_parts=max_results,
2750
+ sample=sample,
2751
+ sample_count=sample_count_use,
2752
+ )
2753
+ variable_sets: List[
2754
+ Union[
2755
+ Tuple[Optional[Any], Dict],
2756
+ List[LazyVariableSet],
2757
+ ],
2758
+ ] = await lazy_variable_controller.render_async(
2759
+ dynamic_block_index=dynamic_block_index,
2760
+ lazy_load=True,
2219
2761
  )
2220
2762
 
2221
- return merge_dict(data, dict(multi_output=True)), is_data_product
2222
- elif isinstance(data, pd.DataFrame):
2223
- if csv_lines_only:
2224
- data = dict(
2225
- table=data.to_csv(header=True, index=False).strip('\n').split('\n')
2226
- )
2227
- else:
2228
- try:
2229
- analysis = variable_manager.get_variable(
2230
- self.pipeline.uuid,
2231
- block_uuid,
2232
- variable_uuid,
2233
- dataframe_analysis_keys=['metadata', 'statistics'],
2234
- partition=execution_partition,
2235
- variable_type=VariableType.DATAFRAME_ANALYSIS,
2236
- )
2237
- except Exception:
2238
- analysis = None
2239
- if analysis is not None and \
2240
- (analysis.get('statistics') or analysis.get('metadata')):
2241
-
2242
- stats = analysis.get('statistics', {})
2243
- column_types = (analysis.get('metadata') or {}).get('column_types', {})
2244
- row_count = stats.get('original_row_count', stats.get('count'))
2245
- column_count = stats.get('original_column_count', len(column_types))
2246
- else:
2247
- row_count, column_count = data.shape
2248
-
2249
- columns_to_display = data.columns.tolist()[:DATAFRAME_ANALYSIS_MAX_COLUMNS]
2250
- data = dict(
2251
- sample_data=dict(
2252
- columns=columns_to_display,
2253
- rows=json.loads(
2254
- data[columns_to_display].to_json(orient='split', date_format='iso'),
2255
- )['data']
2256
- ),
2257
- shape=[row_count, column_count],
2258
- type=DataType.TABLE,
2259
- variable_uuid=variable_uuid,
2260
- )
2261
- return data, True
2262
- elif isinstance(data, pl.DataFrame):
2263
- try:
2264
- analysis = variable_manager.get_variable(
2265
- self.pipeline.uuid,
2266
- block_uuid,
2267
- variable_uuid,
2268
- dataframe_analysis_keys=['statistics'],
2269
- partition=execution_partition,
2270
- variable_type=VariableType.DATAFRAME_ANALYSIS,
2271
- )
2272
- except Exception:
2273
- analysis = None
2274
- if analysis is not None:
2275
- stats = analysis.get('statistics', {})
2276
- row_count = stats.get('original_row_count')
2277
- column_count = stats.get('original_column_count')
2278
- else:
2279
- row_count, column_count = data.shape
2280
- columns_to_display = data.columns[:DATAFRAME_ANALYSIS_MAX_COLUMNS]
2281
- data = dict(
2282
- sample_data=dict(
2283
- columns=columns_to_display,
2284
- rows=[
2285
- list(row.values()) for row in json.loads(
2286
- data[columns_to_display].write_json(row_oriented=True)
2287
- )
2288
- ]
2289
- ),
2290
- shape=[row_count, column_count],
2291
- type=DataType.TABLE,
2292
- variable_uuid=variable_uuid,
2293
- )
2294
- return data, True
2295
- elif is_geo_dataframe(data):
2296
- data = dict(
2297
- text_data=f'''Use the code in a scratchpad to get the output of the block:
2298
-
2299
- from mage_ai.data_preparation.variable_manager import get_variable
2300
- df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2301
- ''',
2302
- type=DataType.TEXT,
2303
- variable_uuid=variable_uuid,
2304
- )
2305
- return data, False
2306
- elif type(data) is str:
2307
- data = dict(
2308
- text_data=data,
2309
- type=DataType.TEXT,
2310
- variable_uuid=variable_uuid,
2311
- )
2312
- return data, False
2313
- elif type(data) is dict or type(data) is list:
2314
- data = dict(
2315
- text_data=simplejson.dumps(
2316
- data,
2317
- default=encode_complex,
2318
- ignore_nan=True,
2319
- ),
2320
- type=DataType.TEXT,
2321
- variable_uuid=variable_uuid,
2322
- )
2323
- return data, False
2324
- elif is_spark_dataframe(data):
2325
- df = data.toPandas()
2326
- columns_to_display = df.columns.tolist()[:DATAFRAME_ANALYSIS_MAX_COLUMNS]
2327
- data = dict(
2328
- sample_data=dict(
2329
- columns=columns_to_display,
2330
- rows=json.loads(
2331
- df[columns_to_display].to_json(orient='split', date_format='iso'),
2332
- )['data']
2333
- ),
2334
- type=DataType.TABLE,
2335
- variable_uuid=variable_uuid,
2763
+ elif is_dynamic:
2764
+ output_pair: List[
2765
+ Optional[Union[Dict, int, str, pd.DataFrame, Any]],
2766
+ ] = await get_outputs_for_dynamic_block_async(
2767
+ self,
2768
+ execution_partition=execution_partition,
2769
+ limit_parts=max_results,
2770
+ sample=sample,
2771
+ sample_count=sample_count_use,
2336
2772
  )
2337
- return data, True
2338
- return data, False
2773
+ output_sets.append(output_pair)
2774
+
2775
+ # Limit the number of dynamic block children we display output for in the UI
2776
+ output_sets = output_sets[:DATAFRAME_SAMPLE_COUNT_PREVIEW]
2777
+ variable_sets = variable_sets[:DATAFRAME_SAMPLE_COUNT_PREVIEW]
2778
+ child_data_sets = await asyncio.gather(*[
2779
+ lazy_variable_set.read_data_async() for lazy_variable_set in variable_sets
2780
+ ])
2781
+
2782
+ return get_outputs_for_display_dynamic_block(
2783
+ self,
2784
+ output_sets,
2785
+ child_data_sets,
2786
+ block_uuid=block_uuid,
2787
+ csv_lines_only=csv_lines_only,
2788
+ exclude_blank_variable_uuids=exclude_blank_variable_uuids,
2789
+ execution_partition=execution_partition,
2790
+ metadata=metadata,
2791
+ sample=sample,
2792
+ sample_count=sample_count_use,
2793
+ )
2794
+
2795
+ def __format_output_data(self, *args, **kwargs) -> Tuple[Dict, bool]:
2796
+ return format_output_data(self, *args, **kwargs)
2339
2797
 
2340
2798
  def __save_outputs_prepare(self, outputs, override_output_variable: bool = False) -> Dict:
2341
2799
  variable_mapping = dict()
@@ -2346,10 +2804,11 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2346
2804
  if not isinstance(o, dict):
2347
2805
  continue
2348
2806
 
2349
- if all(k in o for k in ['variable_uuid', 'text_data']) and \
2350
- (not is_output_variable(o['variable_uuid']) or
2351
- BlockType.SCRATCHPAD == self.type or
2352
- override_output_variable):
2807
+ if all(k in o for k in ['variable_uuid', 'text_data']) and (
2808
+ not is_output_variable(o['variable_uuid'])
2809
+ or BlockType.SCRATCHPAD == self.type
2810
+ or override_output_variable
2811
+ ):
2353
2812
  variable_mapping[o['variable_uuid']] = o['text_data']
2354
2813
 
2355
2814
  self._outputs = outputs
@@ -2377,9 +2836,26 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2377
2836
  self,
2378
2837
  outputs,
2379
2838
  override: bool = False,
2839
+ override_conditionally: bool = False,
2380
2840
  override_outputs: bool = False,
2381
2841
  ) -> None:
2382
2842
  variable_mapping = self.__save_outputs_prepare(outputs)
2843
+
2844
+ if override_conditionally:
2845
+ for variable_uuid, _ in variable_mapping.items():
2846
+ variable = self.get_variable_object(
2847
+ self.uuid,
2848
+ variable_uuid,
2849
+ )
2850
+ if not variable or not variable.variable_type:
2851
+ continue
2852
+
2853
+ # if VariableType
2854
+ # variable = self.get_variable_object(variable_uuid=variable_uuid)
2855
+ # if variable.exists():
2856
+ # variable_mapping.pop(variable_uuid)
2857
+ pass
2858
+
2383
2859
  await self.store_variables_async(
2384
2860
  variable_mapping,
2385
2861
  override=override,
@@ -2403,12 +2879,14 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2403
2879
  def get_pipelines_from_cache(self, block_cache: BlockCache = None) -> List[Dict]:
2404
2880
  if block_cache is None:
2405
2881
  block_cache = BlockCache()
2406
- arr = block_cache.get_pipelines(self)
2882
+ arr = block_cache.get_pipelines(self, self.repo_path)
2407
2883
 
2408
2884
  return unique_by(
2409
2885
  arr,
2410
- lambda x: (f"{(x.get('pipeline') or {}).get('uuid')}_"
2411
- f"{(x.get('pipeline') or {}).get('repo_path')}"),
2886
+ lambda x: (
2887
+ f"{(x.get('pipeline') or {}).get('uuid')}_"
2888
+ f"{(x.get('pipeline') or {}).get('repo_path')}"
2889
+ ),
2412
2890
  )
2413
2891
 
2414
2892
  def to_dict_base(
@@ -2428,7 +2906,7 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2428
2906
  configuration=self.configuration or {},
2429
2907
  downstream_blocks=self.downstream_block_uuids,
2430
2908
  executor_config=self.executor_config,
2431
- executor_type=format_enum(self.executor_type) if self.executor_type else None,
2909
+ executor_type=(format_enum(self.executor_type) if self.executor_type else None),
2432
2910
  has_callback=self.has_callback,
2433
2911
  name=self.name,
2434
2912
  language=language,
@@ -2485,10 +2963,11 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2485
2963
  if include_outputs_use:
2486
2964
  data['outputs'] = self.outputs
2487
2965
 
2488
- if check_if_file_exists and not \
2489
- self.replicated_block and \
2490
- BlockType.GLOBAL_DATA_PRODUCT != self.type:
2491
-
2966
+ if (
2967
+ check_if_file_exists
2968
+ and not self.replicated_block
2969
+ and BlockType.GLOBAL_DATA_PRODUCT != self.type
2970
+ ):
2492
2971
  file_path = self.file_path
2493
2972
  if not os.path.isfile(file_path):
2494
2973
  data['error'] = dict(
@@ -2512,9 +2991,12 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2512
2991
  include_content: bool = False,
2513
2992
  include_outputs: bool = False,
2514
2993
  include_outputs_spark: bool = False,
2515
- sample_count: int = None,
2516
- block_cache: BlockCache = None,
2994
+ sample_count: Optional[int] = None,
2995
+ block_cache: Optional[BlockCache] = None,
2517
2996
  check_if_file_exists: bool = False,
2997
+ disable_output_preview: bool = False,
2998
+ exclude_blank_variable_uuids: bool = False,
2999
+ max_results: Optional[int] = None,
2518
3000
  **kwargs,
2519
3001
  ) -> Dict:
2520
3002
  data = self.to_dict_base(
@@ -2532,25 +3014,44 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2532
3014
 
2533
3015
  if include_outputs:
2534
3016
  include_outputs_use = include_outputs
2535
- if self.is_using_spark() and self.compute_management_enabled():
3017
+ if (
3018
+ self.is_using_spark()
3019
+ and self.compute_management_enabled()
3020
+ and self.pipeline
3021
+ and self.pipeline.type == PipelineType.PYSPARK
3022
+ ):
2536
3023
  include_outputs_use = include_outputs_use and include_outputs_spark
2537
3024
 
2538
3025
  if include_outputs_use:
2539
- data['outputs'] = await self.__outputs_async()
2540
-
2541
- if check_if_file_exists and not \
2542
- self.replicated_block and \
2543
- BlockType.GLOBAL_DATA_PRODUCT != self.type:
3026
+ if (
3027
+ disable_output_preview
3028
+ and self.configuration
3029
+ and self.configuration.get('disable_output_preview', False)
3030
+ ):
3031
+ data['outputs'] = [
3032
+ 'Output preview is disabled for this block. '
3033
+ 'To enable it, go to block settings.',
3034
+ ]
3035
+ else:
3036
+ data['outputs'] = await self.__outputs_async(
3037
+ exclude_blank_variable_uuids=exclude_blank_variable_uuids,
3038
+ max_results=max_results,
3039
+ )
2544
3040
 
2545
- file_path = self.file.file_path
2546
- if not os.path.isfile(file_path):
2547
- data['error'] = dict(
2548
- error='No such file or directory',
2549
- message='You may have moved it or changed its filename. '
2550
- 'Delete the current block to remove it from the pipeline '
2551
- 'or write code and save the pipeline to create a new file at '
2552
- f'{file_path}.',
2553
- )
3041
+ if (
3042
+ check_if_file_exists
3043
+ and not self.replicated_block
3044
+ and BlockType.GLOBAL_DATA_PRODUCT != self.type
3045
+ ):
3046
+ file_path = self.file.file_path
3047
+ if not os.path.isfile(file_path):
3048
+ data['error'] = dict(
3049
+ error='No such file or directory',
3050
+ message='You may have moved it or changed its filename. '
3051
+ 'Delete the current block to remove it from the pipeline '
3052
+ 'or write code and save the pipeline to create a new file at '
3053
+ f'{file_path}.',
3054
+ )
2554
3055
 
2555
3056
  if include_block_metadata:
2556
3057
  data['metadata'] = await self.metadata_async()
@@ -2581,9 +3082,8 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2581
3082
 
2582
3083
  check_upstream_block_order = kwargs.get('check_upstream_block_order', False)
2583
3084
  if 'upstream_blocks' in data and (
2584
- (check_upstream_block_order and
2585
- data['upstream_blocks'] != self.upstream_block_uuids) or
2586
- set(data['upstream_blocks']) != set(self.upstream_block_uuids)
3085
+ (check_upstream_block_order and data['upstream_blocks'] != self.upstream_block_uuids)
3086
+ or set(data['upstream_blocks']) != set(self.upstream_block_uuids)
2587
3087
  ):
2588
3088
  self.__update_upstream_blocks(
2589
3089
  data['upstream_blocks'],
@@ -2618,7 +3118,7 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2618
3118
  if 'has_callback' in data and data['has_callback'] != self.has_callback:
2619
3119
  self.has_callback = data['has_callback']
2620
3120
  if self.has_callback:
2621
- CallbackBlock.create(self.uuid)
3121
+ CallbackBlock.create(self.uuid, self.repo_path)
2622
3122
  self.__update_pipeline_block()
2623
3123
 
2624
3124
  if 'retry_config' in data and data['retry_config'] != self.retry_config:
@@ -2715,10 +3215,7 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2715
3215
  return list(visited)
2716
3216
 
2717
3217
  def run_upstream_blocks(
2718
- self,
2719
- from_notebook: bool = False,
2720
- incomplete_only: bool = False,
2721
- **kwargs
3218
+ self, from_notebook: bool = False, incomplete_only: bool = False, **kwargs
2722
3219
  ) -> None:
2723
3220
  def process_upstream_block(
2724
3221
  block: 'Block',
@@ -2728,10 +3225,12 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2728
3225
  root_blocks.append(block)
2729
3226
  return block.uuid
2730
3227
 
2731
- upstream_blocks = list(filter(
2732
- lambda x: not incomplete_only or BlockStatus.EXECUTED != x.status,
2733
- self.get_all_upstream_blocks(),
2734
- ))
3228
+ upstream_blocks = list(
3229
+ filter(
3230
+ lambda x: not incomplete_only or BlockStatus.EXECUTED != x.status,
3231
+ self.get_all_upstream_blocks(),
3232
+ )
3233
+ )
2735
3234
  root_blocks = []
2736
3235
  upstream_block_uuids = list(
2737
3236
  map(lambda x: process_upstream_block(x, root_blocks), upstream_blocks),
@@ -2769,10 +3268,11 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2769
3268
 
2770
3269
  self.dynamic_block_uuid = dynamic_block_uuid
2771
3270
 
2772
- if self.pipeline \
2773
- and PipelineType.INTEGRATION == self.pipeline.type \
2774
- and self.type in [BlockType.DATA_LOADER, BlockType.DATA_EXPORTER]:
2775
-
3271
+ if (
3272
+ self.pipeline
3273
+ and PipelineType.INTEGRATION == self.pipeline.type
3274
+ and self.type in [BlockType.DATA_LOADER, BlockType.DATA_EXPORTER]
3275
+ ):
2776
3276
  return
2777
3277
 
2778
3278
  test_functions = []
@@ -2815,7 +3315,9 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2815
3315
  test_function = getattr(self.module, func.__name__)
2816
3316
  try:
2817
3317
  sig = signature(test_function)
2818
- has_kwargs = any([p.kind == p.VAR_KEYWORD for p in sig.parameters.values()])
3318
+ has_kwargs = any([
3319
+ p.kind == p.VAR_KEYWORD for p in sig.parameters.values()
3320
+ ])
2819
3321
  if has_kwargs and global_vars is not None and len(global_vars) != 0:
2820
3322
  test_function(*outputs, **global_vars)
2821
3323
  else:
@@ -2826,11 +3328,13 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2826
3328
  stacktrace = traceback.format_exc()
2827
3329
 
2828
3330
  if from_notebook:
2829
- error_json = json.dumps(dict(
2830
- error=str(err),
2831
- message=error_message,
2832
- stacktrace=stacktrace.split('\n'),
2833
- ))
3331
+ error_json = json.dumps(
3332
+ dict(
3333
+ error=str(err),
3334
+ message=error_message,
3335
+ stacktrace=stacktrace.split('\n'),
3336
+ )
3337
+ )
2834
3338
  print(f'[__internal_test__]{error_json}')
2835
3339
  else:
2836
3340
  print('==============================================================')
@@ -2841,9 +3345,11 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2841
3345
  message = f'{tests_passed}/{len(test_functions)} tests passed.'
2842
3346
  if from_notebook:
2843
3347
  if len(test_functions) >= 1:
2844
- success_json = json.dumps(dict(
2845
- message=message,
2846
- ))
3348
+ success_json = json.dumps(
3349
+ dict(
3350
+ message=message,
3351
+ )
3352
+ )
2847
3353
  print(f'[__internal_test__]{success_json}')
2848
3354
  else:
2849
3355
  print('--------------------------------------------------------------')
@@ -2870,11 +3376,12 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2870
3376
  ) -> None:
2871
3377
  if self.pipeline is None:
2872
3378
  return
3379
+
2873
3380
  for uuid, data in variable_mapping.items():
2874
- if type(data) is pd.DataFrame:
3381
+ if isinstance(data, pd.DataFrame):
2875
3382
  if data.shape[1] > DATAFRAME_ANALYSIS_MAX_COLUMNS or shape_only:
2876
- self.pipeline.variable_manager.add_variable(
2877
- self.pipeline.uuid,
3383
+ self.variable_manager.add_variable(
3384
+ self.pipeline_uuid,
2878
3385
  self.uuid,
2879
3386
  uuid,
2880
3387
  dict(
@@ -2885,6 +3392,7 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2885
3392
  ),
2886
3393
  partition=execution_partition,
2887
3394
  variable_type=VariableType.DATAFRAME_ANALYSIS,
3395
+ disable_variable_type_inference=True,
2888
3396
  )
2889
3397
  continue
2890
3398
  if data.shape[0] > DATAFRAME_ANALYSIS_MAX_ROWS:
@@ -2895,14 +3403,15 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2895
3403
  data_for_analysis = data.reset_index(drop=True)
2896
3404
  try:
2897
3405
  from mage_ai.data_cleaner.data_cleaner import clean as clean_data
3406
+
2898
3407
  analysis = clean_data(
2899
3408
  data_for_analysis,
2900
3409
  df_original=data,
2901
3410
  transform=False,
2902
3411
  verbose=False,
2903
3412
  )
2904
- self.pipeline.variable_manager.add_variable(
2905
- self.pipeline.uuid,
3413
+ self.variable_manager.add_variable(
3414
+ self.pipeline_uuid,
2906
3415
  self.uuid,
2907
3416
  uuid,
2908
3417
  dict(
@@ -2914,14 +3423,15 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2914
3423
  partition=execution_partition,
2915
3424
  variable_type=VariableType.DATAFRAME_ANALYSIS,
2916
3425
  )
2917
- except Exception:
2918
- pass
3426
+ except Exception as err:
3427
+ if is_debug():
3428
+ raise err
2919
3429
  # TODO: we use to silently fail, but it looks bad when using BigQuery
2920
3430
  # print('\nFailed to analyze dataframe:')
2921
3431
  # print(traceback.format_exc())
2922
- elif type(data) is pl.DataFrame:
2923
- self.pipeline.variable_manager.add_variable(
2924
- self.pipeline.uuid,
3432
+ elif isinstance(data, pl.DataFrame):
3433
+ self.variable_manager.add_variable(
3434
+ self.pipeline_uuid,
2925
3435
  self.uuid,
2926
3436
  uuid,
2927
3437
  dict(
@@ -2932,6 +3442,7 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2932
3442
  ),
2933
3443
  partition=execution_partition,
2934
3444
  variable_type=VariableType.DATAFRAME_ANALYSIS,
3445
+ disable_variable_type_inference=True,
2935
3446
  )
2936
3447
 
2937
3448
  def set_global_vars(self, global_vars: Dict) -> None:
@@ -2941,9 +3452,15 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2941
3452
 
2942
3453
  def __consolidate_variables(self, variable_mapping: Dict) -> Dict:
2943
3454
  # Consolidate print variables
2944
- output_variables = {k: v for k, v in variable_mapping.items() if is_output_variable(k)}
2945
- print_variables = {k: v for k, v in variable_mapping.items()
2946
- if is_valid_print_variable(k, v, self.uuid)}
3455
+ if BlockType.SCRATCHPAD == self.type:
3456
+ output_variables = {}
3457
+ print_variables = variable_mapping.copy()
3458
+ else:
3459
+ output_variables = {k: v for k, v in variable_mapping.items() if is_output_variable(k)}
3460
+ print_variables = {
3461
+ k: v for k, v in variable_mapping.items()
3462
+ if is_valid_print_variable(k, v, self.uuid)
3463
+ }
2947
3464
 
2948
3465
  print_variables_keys = sorted(print_variables.keys(), key=lambda k: int(k.split('_')[-1]))
2949
3466
 
@@ -2978,6 +3495,10 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
2978
3495
  save_variable_and_reset_state()
2979
3496
  continue
2980
3497
 
3498
+ if json_value.get('msg_type') == 'status':
3499
+ # Do not save status messages
3500
+ continue
3501
+
2981
3502
  if state['msg_key'] is not None and json_value['msg_type'] != state['msg_type']:
2982
3503
  save_variable_and_reset_state()
2983
3504
 
@@ -3034,8 +3555,9 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3034
3555
  """
3035
3556
  if global_vars is None:
3036
3557
  global_vars = dict()
3037
- if ((self.pipeline is not None and self.pipeline.type == PipelineType.DATABRICKS) or
3038
- is_spark_env()):
3558
+ if (
3559
+ self.pipeline is not None and self.pipeline.type == PipelineType.DATABRICKS
3560
+ ) or is_spark_env():
3039
3561
  if not global_vars.get('spark'):
3040
3562
  spark = self.get_spark_session()
3041
3563
  if spark is not None:
@@ -3047,7 +3569,7 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3047
3569
  global_vars['context'] = dict()
3048
3570
 
3049
3571
  # Add pipeline uuid and block uuid to global_vars
3050
- global_vars['pipeline_uuid'] = self.pipeline.uuid if self.pipeline else None
3572
+ global_vars['pipeline_uuid'] = self.pipeline_uuid
3051
3573
  global_vars['block_uuid'] = self.uuid
3052
3574
 
3053
3575
  if dynamic_block_index is not None:
@@ -3055,9 +3577,12 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3055
3577
 
3056
3578
  # Remote blocks
3057
3579
  if global_vars.get('remote_blocks'):
3058
- global_vars['remote_blocks'] = [RemoteBlock.load(
3059
- **remote_block_dict,
3060
- ).get_outputs() for remote_block_dict in global_vars['remote_blocks']]
3580
+ global_vars['remote_blocks'] = [
3581
+ RemoteBlock.load(
3582
+ **remote_block_dict,
3583
+ ).get_outputs()
3584
+ for remote_block_dict in global_vars['remote_blocks']
3585
+ ]
3061
3586
 
3062
3587
  self.global_vars = global_vars
3063
3588
 
@@ -3066,18 +3591,15 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3066
3591
  def get_spark_session(self):
3067
3592
  if not SPARK_ENABLED:
3068
3593
  return None
3069
- if self.spark_init and (not self.pipeline or
3070
- not self.pipeline.spark_config):
3594
+ if self.spark_init and (not self.pipeline or not self.pipeline.spark_config):
3071
3595
  return self.spark
3072
3596
 
3073
3597
  try:
3074
3598
  if self.pipeline and self.pipeline.spark_config:
3075
- spark_config = SparkConfig.load(
3076
- config=self.pipeline.spark_config)
3599
+ spark_config = SparkConfig.load(config=self.pipeline.spark_config)
3077
3600
  else:
3078
3601
  repo_config = RepoConfig(repo_path=self.repo_path)
3079
- spark_config = SparkConfig.load(
3080
- config=repo_config.spark_config)
3602
+ spark_config = SparkConfig.load(config=repo_config.spark_config)
3081
3603
  self.spark = get_spark_session(spark_config)
3082
3604
  except Exception:
3083
3605
  self.spark = None
@@ -3102,18 +3624,19 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3102
3624
  spark_config = SparkConfig.load(config=spark_config)
3103
3625
  if spark_config.use_custom_session:
3104
3626
  return global_vars.get('context', dict()).get(
3105
- spark_config.custom_session_var_name, spark)
3627
+ spark_config.custom_session_var_name, spark
3628
+ )
3106
3629
  return spark
3107
3630
 
3108
- def __store_variables_prepare(
3631
+ def __get_variable_uuids(
3109
3632
  self,
3110
- variable_mapping: Dict,
3111
- execution_partition: str = None,
3112
- override: bool = False,
3113
- override_outputs: bool = False,
3114
- dynamic_block_index: int = None,
3115
- dynamic_block_uuid: str = None,
3116
- ) -> Dict:
3633
+ dynamic_block_index: Optional[int] = None,
3634
+ dynamic_block_uuid: Optional[str] = None,
3635
+ execution_partition: Optional[str] = None,
3636
+ ) -> Tuple[List[str], str, bool]:
3637
+ if self.pipeline is None:
3638
+ return []
3639
+
3117
3640
  self.dynamic_block_uuid = dynamic_block_uuid
3118
3641
 
3119
3642
  block_uuid, changed = uuid_for_output_variables(
@@ -3123,21 +3646,37 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3123
3646
  dynamic_block_uuid=dynamic_block_uuid,
3124
3647
  )
3125
3648
 
3126
- if self.pipeline is None:
3127
- return
3649
+ return (
3650
+ self.variable_manager.get_variables_by_block(
3651
+ self.pipeline_uuid,
3652
+ block_uuid=block_uuid,
3653
+ partition=execution_partition,
3654
+ clean_block_uuid=not changed,
3655
+ ),
3656
+ block_uuid,
3657
+ changed,
3658
+ )
3128
3659
 
3129
- all_variables = self.pipeline.variable_manager.get_variables_by_block(
3130
- self.pipeline.uuid,
3131
- block_uuid=block_uuid,
3132
- partition=execution_partition,
3133
- clean_block_uuid=not changed,
3660
+ def __store_variables_prepare(
3661
+ self,
3662
+ variable_mapping: Dict,
3663
+ execution_partition: Optional[str] = None,
3664
+ override: bool = False,
3665
+ override_outputs: bool = False,
3666
+ dynamic_block_index: Optional[int] = None,
3667
+ dynamic_block_uuid: Optional[str] = None,
3668
+ ) -> Dict:
3669
+ variable_uuids, _block_uuid, _changed = self.__get_variable_uuids(
3670
+ dynamic_block_index=dynamic_block_index,
3671
+ dynamic_block_uuid=dynamic_block_uuid,
3672
+ execution_partition=execution_partition,
3134
3673
  )
3135
3674
 
3136
3675
  variable_mapping = self.__consolidate_variables(variable_mapping)
3137
3676
 
3138
3677
  variable_names = [clean_name_orig(v) for v in variable_mapping]
3139
3678
  removed_variables = []
3140
- for v in all_variables:
3679
+ for v in variable_uuids:
3141
3680
  if v in variable_names:
3142
3681
  continue
3143
3682
 
@@ -3150,16 +3689,97 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3150
3689
  variable_mapping=variable_mapping,
3151
3690
  )
3152
3691
 
3692
+ def delete_variables(
3693
+ self,
3694
+ dynamic_block_index: Optional[int] = None,
3695
+ dynamic_block_uuid: Optional[str] = None,
3696
+ execution_partition: Optional[str] = None,
3697
+ variable_uuids: Optional[List[str]] = None,
3698
+ ) -> None:
3699
+ if self.pipeline is None:
3700
+ return
3701
+
3702
+ variable_uuids_all, block_uuid, _changed = self.__get_variable_uuids(
3703
+ dynamic_block_index=dynamic_block_index,
3704
+ dynamic_block_uuid=dynamic_block_uuid,
3705
+ execution_partition=execution_partition,
3706
+ )
3707
+
3708
+ for variable_uuid in variable_uuids or variable_uuids_all:
3709
+ variable_object = self.variable_manager.get_variable_object(
3710
+ self.pipeline_uuid,
3711
+ block_uuid,
3712
+ variable_uuid,
3713
+ partition=execution_partition,
3714
+ )
3715
+ write_policy = (
3716
+ self.write_settings.batch_settings.mode
3717
+ if self.write_settings and self.write_settings.batch_settings
3718
+ else None
3719
+ )
3720
+ if write_policy and variable_object.data_exists():
3721
+ if ExportWritePolicy.FAIL == write_policy:
3722
+ raise Exception(f'Write policy for block {self.uuid} is {write_policy}.')
3723
+ elif ExportWritePolicy.APPEND == write_policy:
3724
+ return
3725
+
3726
+ variable_object.delete()
3727
+
3728
+ def aggregate_summary_info(self, execution_partition: Optional[str] = None):
3729
+ """
3730
+ Run this only after executing blocks in a notebook so that reading pipelines
3731
+ don’t take forever to load while waiting for all the nested variable folders
3732
+ to be read.
3733
+ """
3734
+ if not VARIABLE_DATA_OUTPUT_META_CACHE or not self.variable_manager:
3735
+ return
3736
+
3737
+ aggregate_summary_info_for_all_variables(
3738
+ self.variable_manager,
3739
+ self.pipeline_uuid,
3740
+ self.uuid,
3741
+ partition=execution_partition,
3742
+ )
3743
+
3153
3744
  def store_variables(
3154
3745
  self,
3155
3746
  variable_mapping: Dict,
3156
- execution_partition: str = None,
3747
+ clean_variable_uuid: Optional[bool] = True,
3748
+ dynamic_block_index: Optional[int] = None,
3749
+ dynamic_block_uuid: Optional[str] = None,
3750
+ execution_partition: Optional[str] = None,
3157
3751
  override: bool = False,
3158
3752
  override_outputs: bool = False,
3159
- spark=None,
3160
- dynamic_block_index: int = None,
3161
- dynamic_block_uuid: str = None,
3162
- ) -> None:
3753
+ skip_delete: Optional[bool] = None,
3754
+ spark: Optional[Any] = None,
3755
+ save_variable_types_only: Optional[Any] = None,
3756
+ ) -> Optional[List[Variable]]:
3757
+ block_uuid, changed = uuid_for_output_variables(
3758
+ self,
3759
+ block_uuid=self.uuid,
3760
+ dynamic_block_index=dynamic_block_index,
3761
+ )
3762
+
3763
+ is_dynamic = is_dynamic_block(self)
3764
+ is_dynamic_child = is_dynamic_block_child(self)
3765
+
3766
+ shared_args = dict(
3767
+ clean_block_uuid=not changed,
3768
+ clean_variable_uuid=not is_dynamic and not is_dynamic_child and clean_variable_uuid,
3769
+ partition=execution_partition,
3770
+ )
3771
+
3772
+ if save_variable_types_only:
3773
+ for variable_uuid, variable_types in variable_mapping.items():
3774
+ self.variable_manager.add_variable_types(
3775
+ self.pipeline_uuid,
3776
+ block_uuid,
3777
+ variable_uuid,
3778
+ variable_types,
3779
+ **shared_args,
3780
+ )
3781
+ return []
3782
+
3163
3783
  variables_data = self.__store_variables_prepare(
3164
3784
  variable_mapping,
3165
3785
  execution_partition,
@@ -3168,40 +3788,45 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3168
3788
  dynamic_block_index=dynamic_block_index,
3169
3789
  )
3170
3790
 
3171
- block_uuid, changed = uuid_for_output_variables(
3172
- self,
3173
- block_uuid=self.uuid,
3174
- dynamic_block_index=dynamic_block_index,
3175
- )
3176
-
3177
- is_dynamic_child = is_dynamic_block_child(self)
3178
- if is_dynamic_child:
3791
+ if not skip_delete and is_dynamic_child:
3792
+ # execute_block_function will take care of this if decorated function is a generator
3179
3793
  delete_variable_objects_for_dynamic_child(
3180
3794
  self,
3181
3795
  dynamic_block_index=dynamic_block_index,
3182
3796
  execution_partition=execution_partition,
3183
3797
  )
3184
3798
 
3799
+ variables = []
3185
3800
  for uuid, data in variables_data['variable_mapping'].items():
3186
- if spark is not None and self.pipeline.type == PipelineType.PYSPARK \
3187
- and type(data) is pd.DataFrame:
3801
+ if (
3802
+ spark is not None
3803
+ and self.pipeline is not None
3804
+ and self.pipeline.type == PipelineType.PYSPARK
3805
+ and isinstance(data, pd.DataFrame)
3806
+ ):
3188
3807
  data = spark.createDataFrame(data)
3189
- self.pipeline.variable_manager.add_variable(
3190
- self.pipeline.uuid,
3191
- block_uuid,
3192
- uuid,
3193
- data,
3194
- partition=execution_partition,
3195
- clean_block_uuid=not changed,
3196
- )
3197
3808
 
3198
- if not is_dynamic_child:
3199
- for uuid in variables_data['removed_variables']:
3200
- self.pipeline.variable_manager.delete_variable(
3201
- self.pipeline.uuid,
3809
+ variables.append(
3810
+ self.variable_manager.add_variable(
3811
+ self.pipeline_uuid,
3202
3812
  block_uuid,
3203
3813
  uuid,
3814
+ data,
3815
+ resource_usage=self.resource_usage,
3816
+ write_batch_settings=self.write_batch_settings,
3817
+ write_chunks=self.write_chunks,
3818
+ **shared_args,
3204
3819
  )
3820
+ )
3821
+ if not skip_delete and not is_dynamic_child and variables_data.get('removed_variables'):
3822
+ self.delete_variables(
3823
+ dynamic_block_index=dynamic_block_index,
3824
+ dynamic_block_uuid=dynamic_block_uuid,
3825
+ execution_partition=execution_partition,
3826
+ variable_uuids=variables_data['removed_variables'],
3827
+ )
3828
+
3829
+ return variables
3205
3830
 
3206
3831
  async def store_variables_async(
3207
3832
  self,
@@ -3239,19 +3864,22 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3239
3864
  if spark is not None and type(data) is pd.DataFrame:
3240
3865
  data = spark.createDataFrame(data)
3241
3866
 
3242
- await self.pipeline.variable_manager.add_variable_async(
3243
- self.pipeline.uuid,
3867
+ await self.variable_manager.add_variable_async(
3868
+ self.pipeline_uuid,
3244
3869
  block_uuid,
3245
3870
  uuid,
3246
3871
  data,
3247
3872
  partition=execution_partition,
3248
3873
  clean_block_uuid=not changed,
3874
+ write_batch_settings=self.write_batch_settings,
3875
+ write_chunks=self.write_chunks,
3876
+ resource_usage=self.resource_usage,
3249
3877
  )
3250
3878
 
3251
3879
  if not is_dynamic_child:
3252
3880
  for uuid in variables_data['removed_variables']:
3253
- self.pipeline.variable_manager.delete_variable(
3254
- self.pipeline.uuid,
3881
+ self.variable_manager.delete_variable(
3882
+ self.pipeline_uuid,
3255
3883
  block_uuid,
3256
3884
  uuid,
3257
3885
  clean_block_uuid=not changed,
@@ -3280,8 +3908,8 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3280
3908
  for b in self.upstream_blocks:
3281
3909
  for v in b.output_variables(execution_partition=execution_partition):
3282
3910
  objs.append(
3283
- self.pipeline.variable_manager.get_variable_object(
3284
- self.pipeline.uuid,
3911
+ self.get_variable_object(
3912
+ self.pipeline_uuid,
3285
3913
  b.uuid,
3286
3914
  v,
3287
3915
  partition=execution_partition,
@@ -3297,6 +3925,7 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3297
3925
  from_notebook: bool = False,
3298
3926
  global_vars: Dict = None,
3299
3927
  input_args: List[Any] = None,
3928
+ max_results: Optional[int] = None,
3300
3929
  block_uuid: str = None,
3301
3930
  ) -> List[str]:
3302
3931
  return output_variables(
@@ -3308,6 +3937,7 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3308
3937
  from_notebook=from_notebook,
3309
3938
  global_vars=global_vars,
3310
3939
  input_args=input_args,
3940
+ max_results=max_results,
3311
3941
  )
3312
3942
 
3313
3943
  def output_variable_objects(
@@ -3331,12 +3961,15 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3331
3961
  if len(output_variables) == 0:
3332
3962
  return []
3333
3963
 
3334
- variable_objects = [self.pipeline.variable_manager.get_variable_object(
3335
- self.pipeline.uuid,
3336
- self.uuid,
3337
- v,
3338
- partition=execution_partition,
3339
- ) for v in output_variables]
3964
+ variable_objects = [
3965
+ self.variable_manager.get_variable_object(
3966
+ self.pipeline_uuid,
3967
+ self.uuid,
3968
+ v,
3969
+ partition=execution_partition,
3970
+ )
3971
+ for v in output_variables
3972
+ ]
3340
3973
  if variable_type is not None:
3341
3974
  variable_objects = [v for v in variable_objects if v.variable_type == variable_type]
3342
3975
  return variable_objects
@@ -3372,8 +4005,8 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3372
4005
  ) -> Any:
3373
4006
  if self.pipeline is None:
3374
4007
  return []
3375
- return self.pipeline.variable_manager.get_variable_object(
3376
- self.pipeline.uuid,
4008
+ return self.variable_manager.get_variable_object(
4009
+ self.pipeline_uuid,
3377
4010
  self.uuid,
3378
4011
  variable_uuid,
3379
4012
  partition=execution_partition,
@@ -3410,32 +4043,41 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3410
4043
  self.uuid = new_uuid
3411
4044
 
3412
4045
  # This file has a path in its file_source that must be updated.
3413
- if project_platform_activated() and \
3414
- self.file_source_path() and \
3415
- add_absolute_path(self.file_source_path()) == self.file_path:
3416
-
3417
- # /home/src/data-vault/perftools/mage/data_loaders/team/illusory_glitter
4046
+ if (
4047
+ project_platform_activated()
4048
+ and self.file_source_path()
4049
+ and add_absolute_path(self.file_source_path()) == self.file_path
4050
+ ):
4051
+ # /home/src/data-vault/perftools/mage/data_loaders/team/illusory_glitter
3418
4052
  old_file_path_without_extension = str(Path(old_file_path).with_suffix(''))
3419
4053
  # /home/src/data-vault/perftools/mage/data_loaders/team
3420
- old_file_path_without_uuid = str(Path(old_file_path_without_extension.replace(
3421
- str(Path(old_uuid)),
3422
- '',
3423
- )))
4054
+ old_file_path_without_uuid = str(
4055
+ Path(
4056
+ old_file_path_without_extension.replace(
4057
+ str(Path(old_uuid)),
4058
+ '',
4059
+ )
4060
+ )
4061
+ )
3424
4062
 
3425
- # perftools/mage/data_loaders/team
4063
+ # perftools/mage/data_loaders/team
3426
4064
  old_file_path_without_repo_path = remove_base_repo_path(old_file_path_without_uuid)
3427
- # perftools/mage
4065
+ # perftools/mage
3428
4066
  path_without_block_directory = str(old_file_path_without_repo_path).split(
3429
4067
  directory_name,
3430
4068
  )[0]
3431
4069
 
3432
4070
  file_extension_new = Path(self.uuid).suffix or file_extension
3433
4071
  # perftools/mage/data_loaders/load_titanic.py
3434
- new_path = str(Path(os.path.join(
3435
- path_without_block_directory,
3436
- directory_name,
3437
- str(Path(self.uuid).with_suffix('')),
3438
- )).with_suffix(file_extension_new))
4072
+ new_path = str(
4073
+ Path(
4074
+ os.path.join(
4075
+ path_without_block_directory,
4076
+ directory_name,
4077
+ str(Path(self.uuid).with_suffix('')),
4078
+ )
4079
+ ).with_suffix(file_extension_new)
4080
+ )
3439
4081
 
3440
4082
  configuration = self.configuration or {}
3441
4083
  if not configuration.get('file_source'):
@@ -3444,7 +4086,16 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3444
4086
  self.configuration = configuration
3445
4087
 
3446
4088
  # This has to be here
3447
- new_file_path = self.file_path
4089
+ new_file_path, new_file_path_relative = self.build_file_path_directory(
4090
+ block_uuid=new_uuid,
4091
+ )
4092
+
4093
+ configuration = self.configuration or {}
4094
+ if not configuration.get('file_source'):
4095
+ configuration['file_source'] = {}
4096
+ configuration['file_path'] = new_file_path_relative
4097
+ configuration['file_source']['path'] = new_file_path_relative
4098
+ self.configuration = configuration
3448
4099
 
3449
4100
  if self.pipeline is not None:
3450
4101
  DX_PRINTER.critical(
@@ -3456,7 +4107,7 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3456
4107
  name=self.name,
3457
4108
  uuid=self.uuid,
3458
4109
  file_path=new_file_path,
3459
- pipeline=self.pipeline.uuid,
4110
+ pipeline=self.pipeline_uuid,
3460
4111
  repo_path=self.pipeline.repo_path,
3461
4112
  configuration=self.configuration,
3462
4113
  __uuid='__update_name',
@@ -3472,20 +4123,23 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3472
4123
  )
3473
4124
 
3474
4125
  if not self.replicated_block and BlockType.GLOBAL_DATA_PRODUCT != self.type:
3475
- if os.path.exists(new_file_path):
3476
- raise Exception(f'Block {new_uuid} already exists. Please use a different name.')
4126
+ if new_file_path and os.path.exists(new_file_path):
4127
+ raise Exception(
4128
+ f'Block {new_uuid} already exists at {new_file_path}. '
4129
+ 'Please use a different name.'
4130
+ )
3477
4131
 
3478
4132
  parent_dir = os.path.dirname(new_file_path)
3479
4133
  os.makedirs(parent_dir, exist_ok=True)
3480
4134
 
3481
4135
  if detach:
3482
- """"
4136
+ """ "
3483
4137
  Detaching a block creates a copy of the block file while keeping the existing block
3484
4138
  file the same. Without detaching a block, the existing block file is simply renamed.
3485
4139
  """
3486
4140
  with open(new_file_path, 'w') as f:
3487
4141
  f.write(block_content)
3488
- else:
4142
+ elif os.path.exists(old_file_path):
3489
4143
  os.rename(old_file_path, new_file_path)
3490
4144
 
3491
4145
  if self.pipeline is not None:
@@ -3496,11 +4150,12 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3496
4150
  from mage_ai.data_preparation.models.block.block_factory import (
3497
4151
  BlockFactory,
3498
4152
  )
4153
+
3499
4154
  """"
3500
4155
  New block added to pipeline, so it must be added to the block cache.
3501
4156
  Old block no longer in pipeline, so it must be removed from block cache.
3502
4157
  """
3503
- cache.add_pipeline(self, self.pipeline)
4158
+ cache.add_pipeline(self, self.pipeline, self.pipeline.repo_path)
3504
4159
  old_block = BlockFactory.get_block(
3505
4160
  old_uuid,
3506
4161
  old_uuid,
@@ -3508,12 +4163,16 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3508
4163
  language=self.language,
3509
4164
  pipeline=self.pipeline,
3510
4165
  )
3511
- cache.remove_pipeline(old_block, self.pipeline.uuid, self.pipeline.repo_path)
4166
+ cache.remove_pipeline(old_block, self.pipeline_uuid, self.pipeline.repo_path)
3512
4167
  else:
3513
- cache.move_pipelines(self, dict(
3514
- type=self.type,
3515
- uuid=old_uuid,
3516
- ))
4168
+ cache.move_pipelines(
4169
+ self,
4170
+ dict(
4171
+ type=self.type,
4172
+ uuid=old_uuid,
4173
+ ),
4174
+ self.pipeline.repo_path,
4175
+ )
3517
4176
 
3518
4177
  def __update_pipeline_block(self, widget=False) -> None:
3519
4178
  if self.pipeline is None:
@@ -3587,7 +4246,9 @@ class SensorBlock(Block):
3587
4246
  block_function: Callable,
3588
4247
  input_vars: List,
3589
4248
  from_notebook: bool = False,
3590
- global_vars: Dict = None,
4249
+ global_vars: Optional[Dict] = None,
4250
+ *args,
4251
+ **kwargs,
3591
4252
  ) -> List:
3592
4253
  if from_notebook:
3593
4254
  return super().execute_block_function(
@@ -3603,8 +4264,9 @@ class SensorBlock(Block):
3603
4264
  use_global_vars = has_kwargs and global_vars is not None and len(global_vars) != 0
3604
4265
  args = input_vars if has_args else []
3605
4266
  while True:
3606
- condition = block_function(*args, **global_vars) \
3607
- if use_global_vars else block_function()
4267
+ condition = (
4268
+ block_function(*args, **global_vars) if use_global_vars else block_function()
4269
+ )
3608
4270
  if condition:
3609
4271
  break
3610
4272
  print('Sensor sleeping for 1 minute...')
@@ -3624,7 +4286,7 @@ class AddonBlock(Block):
3624
4286
  global_vars = merge_dict(
3625
4287
  global_vars or dict(),
3626
4288
  dict(
3627
- pipeline_uuid=self.pipeline.uuid,
4289
+ pipeline_uuid=self.pipeline_uuid,
3628
4290
  block_uuid=self.uuid,
3629
4291
  pipeline_run=pipeline_run,
3630
4292
  ),
@@ -3643,9 +4305,7 @@ class AddonBlock(Block):
3643
4305
  global_vars = merge_dict(parent_block.global_vars, global_vars)
3644
4306
  global_vars['parent_block_uuid'] = parent_block.uuid
3645
4307
 
3646
- if parent_block.pipeline and \
3647
- PipelineType.INTEGRATION == parent_block.pipeline.type:
3648
-
4308
+ if parent_block.pipeline and PipelineType.INTEGRATION == parent_block.pipeline.type:
3649
4309
  template_runtime_configuration = parent_block.template_runtime_configuration
3650
4310
  index = template_runtime_configuration.get('index', None)
3651
4311
  is_last_block_run = template_runtime_configuration.get('is_last_block_run', False)
@@ -3671,7 +4331,7 @@ class ConditionalBlock(AddonBlock):
3671
4331
  global_vars: Dict = None,
3672
4332
  logger: Logger = None,
3673
4333
  logging_tags: Dict = None,
3674
- **kwargs
4334
+ **kwargs,
3675
4335
  ) -> bool:
3676
4336
  with self._redirect_streams(
3677
4337
  logger=logger,
@@ -3715,11 +4375,11 @@ class ConditionalBlock(AddonBlock):
3715
4375
 
3716
4376
  class CallbackBlock(AddonBlock):
3717
4377
  @classmethod
3718
- def create(cls, orig_block_name) -> 'CallbackBlock':
4378
+ def create(cls, orig_block_name, repo_path: str) -> 'CallbackBlock':
3719
4379
  return Block.create(
3720
4380
  f'{clean_name_orig(orig_block_name)}_callback',
3721
4381
  BlockType.CALLBACK,
3722
- get_repo_path(),
4382
+ repo_path,
3723
4383
  language=BlockLanguage.PYTHON,
3724
4384
  )
3725
4385
 
@@ -3738,7 +4398,7 @@ class CallbackBlock(AddonBlock):
3738
4398
  from_notebook: bool = False,
3739
4399
  metadata: Dict = None,
3740
4400
  upstream_block_uuids_override: List[str] = None,
3741
- **kwargs
4401
+ **kwargs,
3742
4402
  ) -> None:
3743
4403
  with self._redirect_streams(
3744
4404
  logger=logger,
@@ -3750,7 +4410,7 @@ class CallbackBlock(AddonBlock):
3750
4410
  global_vars,
3751
4411
  parent_block,
3752
4412
  dynamic_block_index=dynamic_block_index,
3753
- **kwargs
4413
+ **kwargs,
3754
4414
  )
3755
4415
 
3756
4416
  callback_functions = []
@@ -3832,12 +4492,10 @@ class CallbackBlock(AddonBlock):
3832
4492
  # As of version 0.8.81, callback functions have access to the parent block’s
3833
4493
  # data output. If the callback function has any positional arguments, we will
3834
4494
  # pass in the input variables as positional arguments.
3835
- if not input_vars or any(
3836
- [
3837
- p.kind not in [p.KEYWORD_ONLY, p.VAR_KEYWORD]
3838
- for p in sig.parameters.values()
3839
- ]
3840
- ):
4495
+ if not input_vars or any([
4496
+ p.kind not in [p.KEYWORD_ONLY, p.VAR_KEYWORD]
4497
+ for p in sig.parameters.values()
4498
+ ]):
3841
4499
  inner_function(
3842
4500
  *input_vars,
3843
4501
  **callback_kwargs,
@@ -3846,9 +4504,12 @@ class CallbackBlock(AddonBlock):
3846
4504
  # a user has already written callback functions with only keyword arguments.
3847
4505
  else:
3848
4506
  inner_function(
3849
- **merge_dict(callback_kwargs, dict(
3850
- __input=outputs_from_input_vars,
3851
- )),
4507
+ **merge_dict(
4508
+ callback_kwargs,
4509
+ dict(
4510
+ __input=outputs_from_input_vars,
4511
+ ),
4512
+ ),
3852
4513
  )
3853
4514
 
3854
4515
  def update_content(self, content, widget=False) -> 'CallbackBlock':
@@ -3871,9 +4532,11 @@ class CallbackBlock(AddonBlock):
3871
4532
  def custom_code(callback_status: CallbackStatus = CallbackStatus.SUCCESS, *args, **kwargs):
3872
4533
  # If the decorator is just @callback with no arguments, default to success callback
3873
4534
  if isfunction(callback_status):
4535
+
3874
4536
  def func(callback_status_inner, *args, **kwargs):
3875
4537
  if callback_status_inner == CallbackStatus.SUCCESS:
3876
4538
  return callback_status(*args, **kwargs)
4539
+
3877
4540
  decorated_functions.append(func)
3878
4541
  return func
3879
4542
 
@@ -3887,6 +4550,7 @@ class CallbackBlock(AddonBlock):
3887
4550
  def func(callback_status_inner):
3888
4551
  if callback_status_inner == callback_status:
3889
4552
  return function
4553
+
3890
4554
  decorated_functions.append(func)
3891
4555
 
3892
4556
  return inner