prefect 3.6.13__py3-none-any.whl → 3.6.13.dev2__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.
Files changed (220) hide show
  1. prefect/_build_info.py +3 -3
  2. prefect/_internal/compatibility/blocks.py +0 -18
  3. prefect/client/schemas/filters.py +0 -24
  4. prefect/flow_engine.py +10 -192
  5. prefect/flows.py +2 -61
  6. prefect/results.py +21 -262
  7. prefect/runner/runner.py +82 -29
  8. prefect/runner/storage.py +2 -3
  9. prefect/server/schemas/filters.py +0 -45
  10. prefect/server/ui-v2/assets/{artifact-card-CUEaRDGw.js → artifact-card-C8JEQRHl.js} +2 -2
  11. prefect/server/ui-v2/assets/{artifact-card-CUEaRDGw.js.map → artifact-card-C8JEQRHl.js.map} +1 -1
  12. prefect/server/ui-v2/assets/{artifact._id-Ca6VCUS0.js → artifact._id-67V8kTg9.js} +2 -2
  13. prefect/server/ui-v2/assets/{artifact._id-Ca6VCUS0.js.map → artifact._id-67V8kTg9.js.map} +1 -1
  14. prefect/server/ui-v2/assets/{automation-wizard-z26pICBl.js → automation-wizard-DuxZ47Nn.js} +2 -2
  15. prefect/server/ui-v2/assets/{automation-wizard-z26pICBl.js.map → automation-wizard-DuxZ47Nn.js.map} +1 -1
  16. prefect/server/ui-v2/assets/{automation._id-DuodjY5t.js → automation._id-D8S8r4Ji.js} +2 -2
  17. prefect/server/ui-v2/assets/{automation._id-DuodjY5t.js.map → automation._id-D8S8r4Ji.js.map} +1 -1
  18. prefect/server/ui-v2/assets/{automation_._id.edit-CcuJhc-y.js → automation_._id.edit-C1fOM0Hx.js} +2 -2
  19. prefect/server/ui-v2/assets/{automation_._id.edit-CcuJhc-y.js.map → automation_._id.edit-C1fOM0Hx.js.map} +1 -1
  20. prefect/server/ui-v2/assets/{automations-header-CgOWwuc6.js → automations-header-DrijTNGi.js} +2 -2
  21. prefect/server/ui-v2/assets/{automations-header-CgOWwuc6.js.map → automations-header-DrijTNGi.js.map} +1 -1
  22. prefect/server/ui-v2/assets/{base-job-template-form-section-DN9HyjM5.js → base-job-template-form-section-BDkqOkpe.js} +2 -2
  23. prefect/server/ui-v2/assets/{base-job-template-form-section-DN9HyjM5.js.map → base-job-template-form-section-BDkqOkpe.js.map} +1 -1
  24. prefect/server/ui-v2/assets/{block-type-details-rrToxL5r.js → block-type-details-CrFHWH5D.js} +2 -2
  25. prefect/server/ui-v2/assets/{block-type-details-rrToxL5r.js.map → block-type-details-CrFHWH5D.js.map} +1 -1
  26. prefect/server/ui-v2/assets/block-type-logo-WQNm0PzP.js +2 -0
  27. prefect/server/ui-v2/assets/{block-type-logo-DURScH8H.js.map → block-type-logo-WQNm0PzP.js.map} +1 -1
  28. prefect/server/ui-v2/assets/{block._id-Bue6lnrN.js → block._id-C3gN1Ae8.js} +2 -2
  29. prefect/server/ui-v2/assets/{block._id-Bue6lnrN.js.map → block._id-C3gN1Ae8.js.map} +1 -1
  30. prefect/server/ui-v2/assets/{block_._id.edit-QY0OzN_b.js → block_._id.edit-DzPuSvat.js} +2 -2
  31. prefect/server/ui-v2/assets/{block_._id.edit-QY0OzN_b.js.map → block_._id.edit-DzPuSvat.js.map} +1 -1
  32. prefect/server/ui-v2/assets/{catalog-CGjRGWv6.js → catalog-T-7MqsyE.js} +2 -2
  33. prefect/server/ui-v2/assets/{catalog-CGjRGWv6.js.map → catalog-T-7MqsyE.js.map} +1 -1
  34. prefect/server/ui-v2/assets/{catalog_._slug-BMFNZhEb.js → catalog_._slug-DMOI61fc.js} +2 -2
  35. prefect/server/ui-v2/assets/{catalog_._slug-BMFNZhEb.js.map → catalog_._slug-DMOI61fc.js.map} +1 -1
  36. prefect/server/ui-v2/assets/{catalog_._slug_.create-CuFA3kUC.js → catalog_._slug_.create-l9bGUPi-.js} +2 -2
  37. prefect/server/ui-v2/assets/{catalog_._slug_.create-CuFA3kUC.js.map → catalog_._slug_.create-l9bGUPi-.js.map} +1 -1
  38. prefect/server/ui-v2/assets/{collapsible-Ell2FjrX.js → collapsible-DM-Ze5-Y.js} +2 -2
  39. prefect/server/ui-v2/assets/{collapsible-Ell2FjrX.js.map → collapsible-DM-Ze5-Y.js.map} +1 -1
  40. prefect/server/ui-v2/assets/{concurrency-limit._id-Rr1ysUct.js → concurrency-limit._id-BHEt9viF.js} +2 -2
  41. prefect/server/ui-v2/assets/{concurrency-limit._id-Rr1ysUct.js.map → concurrency-limit._id-BHEt9viF.js.map} +1 -1
  42. prefect/server/ui-v2/assets/{create-ewA3uq_h.js → create-BMxUOyN5.js} +2 -2
  43. prefect/server/ui-v2/assets/{create-ewA3uq_h.js.map → create-BMxUOyN5.js.map} +1 -1
  44. prefect/server/ui-v2/assets/{create-CQK8-uO1.js → create-Cq-EAHVk.js} +2 -2
  45. prefect/server/ui-v2/assets/{create-CQK8-uO1.js.map → create-Cq-EAHVk.js.map} +1 -1
  46. prefect/server/ui-v2/assets/{data-table-Bx1uYX_M.js → data-table-Bw8TZx9D.js} +2 -2
  47. prefect/server/ui-v2/assets/{data-table-Bx1uYX_M.js.map → data-table-Bw8TZx9D.js.map} +1 -1
  48. prefect/server/ui-v2/assets/{delete-confirmation-dialog-CqKsUEj_.js → delete-confirmation-dialog-CTdWZlGM.js} +2 -2
  49. prefect/server/ui-v2/assets/{delete-confirmation-dialog-CqKsUEj_.js.map → delete-confirmation-dialog-CTdWZlGM.js.map} +1 -1
  50. prefect/server/ui-v2/assets/{deployment-action-header-Bz5COdER.js → deployment-action-header-BObYqmI_.js} +2 -2
  51. prefect/server/ui-v2/assets/{deployment-action-header-Bz5COdER.js.map → deployment-action-header-BObYqmI_.js.map} +1 -1
  52. prefect/server/ui-v2/assets/{deployment-form-CrlZlNoj.js → deployment-form-CntyhRrK.js} +3 -3
  53. prefect/server/ui-v2/assets/{deployment-form-CrlZlNoj.js.map → deployment-form-CntyhRrK.js.map} +1 -1
  54. prefect/server/ui-v2/assets/{deployment-links-D9ZR_vmp.js → deployment-links-DBUSCaxB.js} +2 -2
  55. prefect/server/ui-v2/assets/{deployment-links-D9ZR_vmp.js.map → deployment-links-DBUSCaxB.js.map} +1 -1
  56. prefect/server/ui-v2/assets/{deployment._id-DvHhx-qN.js → deployment._id-BXanVFA8.js} +2 -2
  57. prefect/server/ui-v2/assets/{deployment._id-DvHhx-qN.js.map → deployment._id-BXanVFA8.js.map} +1 -1
  58. prefect/server/ui-v2/assets/{deployment._id-BV0rSqba.js → deployment._id-Q5cvHMX9.js} +2 -2
  59. prefect/server/ui-v2/assets/{deployment._id-BV0rSqba.js.map → deployment._id-Q5cvHMX9.js.map} +1 -1
  60. prefect/server/ui-v2/assets/deployment_._id.duplicate-DB-4hHHc.js +2 -0
  61. prefect/server/ui-v2/assets/{deployment_._id.duplicate-BrEOenqP.js.map → deployment_._id.duplicate-DB-4hHHc.js.map} +1 -1
  62. prefect/server/ui-v2/assets/deployment_._id.edit-CWf1RIGy.js +2 -0
  63. prefect/server/ui-v2/assets/{deployment_._id.edit-BbYKPK42.js.map → deployment_._id.edit-CWf1RIGy.js.map} +1 -1
  64. prefect/server/ui-v2/assets/{deployment_._id.run-Dv7S_MJR.js → deployment_._id.run-CoHGXFoM.js} +2 -2
  65. prefect/server/ui-v2/assets/{deployment_._id.run-Dv7S_MJR.js.map → deployment_._id.run-CoHGXFoM.js.map} +1 -1
  66. prefect/server/ui-v2/assets/{dropdown-menu-e0Fqb6aw.js → dropdown-menu-CT-s-V7d.js} +2 -2
  67. prefect/server/ui-v2/assets/{dropdown-menu-e0Fqb6aw.js.map → dropdown-menu-CT-s-V7d.js.map} +1 -1
  68. prefect/server/ui-v2/assets/{event._eventDate._eventId-Cp4UmGqq.js → event._eventDate._eventId-BITo_GYL.js} +2 -2
  69. prefect/server/ui-v2/assets/{event._eventDate._eventId-Cp4UmGqq.js.map → event._eventDate._eventId-BITo_GYL.js.map} +1 -1
  70. prefect/server/ui-v2/assets/flow-run-graph-DxIl6fzW.js +2 -0
  71. prefect/server/ui-v2/assets/{flow-run-graph-BrqoR3E2.js.map → flow-run-graph-DxIl6fzW.js.map} +1 -1
  72. prefect/server/ui-v2/assets/{flow-run._id-BOp38Pbq.js → flow-run._id-Bbm9OpDi.js} +2 -2
  73. prefect/server/ui-v2/assets/{flow-run._id-BOp38Pbq.js.map → flow-run._id-Bbm9OpDi.js.map} +1 -1
  74. prefect/server/ui-v2/assets/{flow-run._id-DJnTDEN_.js → flow-run._id-BtSgRDtA.js} +2 -2
  75. prefect/server/ui-v2/assets/{flow-run._id-DJnTDEN_.js.map → flow-run._id-BtSgRDtA.js.map} +1 -1
  76. prefect/server/ui-v2/assets/flow-run._id-DJuMECRh.js +4 -0
  77. prefect/server/ui-v2/assets/flow-run._id-DJuMECRh.js.map +1 -0
  78. prefect/server/ui-v2/assets/{flow-runs-pagination-Bq2ZUzM6.js → flow-runs-pagination-LrU9Aio8.js} +2 -2
  79. prefect/server/ui-v2/assets/{flow-runs-pagination-Bq2ZUzM6.js.map → flow-runs-pagination-LrU9Aio8.js.map} +1 -1
  80. prefect/server/ui-v2/assets/{flow._id-eCBL95rg.js → flow._id-BJBRokk4.js} +2 -2
  81. prefect/server/ui-v2/assets/{flow._id-eCBL95rg.js.map → flow._id-BJBRokk4.js.map} +1 -1
  82. prefect/server/ui-v2/assets/{form-DNerk3LS.js → form-CVSlEnl8.js} +2 -2
  83. prefect/server/ui-v2/assets/{form-DNerk3LS.js.map → form-CVSlEnl8.js.map} +1 -1
  84. prefect/server/ui-v2/assets/{header-DwagHBlF.js → header-4plZZheZ.js} +2 -2
  85. prefect/server/ui-v2/assets/{header-DwagHBlF.js.map → header-4plZZheZ.js.map} +1 -1
  86. prefect/server/ui-v2/assets/{header-huSvwxKI.js → header-9rXZ4r39.js} +2 -2
  87. prefect/server/ui-v2/assets/{header-huSvwxKI.js.map → header-9rXZ4r39.js.map} +1 -1
  88. prefect/server/ui-v2/assets/{header-B0ejRncu.js → header-D3uM8_xM.js} +2 -2
  89. prefect/server/ui-v2/assets/{header-B0ejRncu.js.map → header-D3uM8_xM.js.map} +1 -1
  90. prefect/server/ui-v2/assets/{index-DI2DC5gd.js → index-6OsEYhIi.js} +2 -2
  91. prefect/server/ui-v2/assets/{index-DI2DC5gd.js.map → index-6OsEYhIi.js.map} +1 -1
  92. prefect/server/ui-v2/assets/{index-BzN9bQeM.js → index-B7xsJ-QH.js} +2 -2
  93. prefect/server/ui-v2/assets/{index-BzN9bQeM.js.map → index-B7xsJ-QH.js.map} +1 -1
  94. prefect/server/ui-v2/assets/{index-CnIJUujl.js → index-BeoNC2tJ.js} +2 -2
  95. prefect/server/ui-v2/assets/{index-CnIJUujl.js.map → index-BeoNC2tJ.js.map} +1 -1
  96. prefect/server/ui-v2/assets/{index-CTnoa3Ho.js → index-BfJLUM7N.js} +2 -2
  97. prefect/server/ui-v2/assets/{index-CTnoa3Ho.js.map → index-BfJLUM7N.js.map} +1 -1
  98. prefect/server/ui-v2/assets/{index-CWkbSdxY.js → index-Bj0OOguP.js} +2 -2
  99. prefect/server/ui-v2/assets/{index-CWkbSdxY.js.map → index-Bj0OOguP.js.map} +1 -1
  100. prefect/server/ui-v2/assets/{index-CBhi1P9g.js → index-BlpD74iH.js} +2 -2
  101. prefect/server/ui-v2/assets/{index-CBhi1P9g.js.map → index-BlpD74iH.js.map} +1 -1
  102. prefect/server/ui-v2/assets/{index-D5RdrxkU.js → index-BvSl3DKP.js} +8 -8
  103. prefect/server/ui-v2/assets/index-BvSl3DKP.js.map +1 -0
  104. prefect/server/ui-v2/assets/{index-BU4yZRd3.js → index-CDwJvjUM.js} +2 -2
  105. prefect/server/ui-v2/assets/{index-BU4yZRd3.js.map → index-CDwJvjUM.js.map} +1 -1
  106. prefect/server/ui-v2/assets/{index-CRDz4nhM.js → index-CDyLkbVG.js} +2 -2
  107. prefect/server/ui-v2/assets/{index-CRDz4nhM.js.map → index-CDyLkbVG.js.map} +1 -1
  108. prefect/server/ui-v2/assets/{index-Cutg_A1j.js → index-ChIrfjIW.js} +2 -2
  109. prefect/server/ui-v2/assets/{index-Cutg_A1j.js.map → index-ChIrfjIW.js.map} +1 -1
  110. prefect/server/ui-v2/assets/{index-BhALpenO.js → index-CzCSgCK-.js} +2 -2
  111. prefect/server/ui-v2/assets/{index-BhALpenO.js.map → index-CzCSgCK-.js.map} +1 -1
  112. prefect/server/ui-v2/assets/{index-Dg_duvDx.js → index-D5v9S-lB.js} +2 -2
  113. prefect/server/ui-v2/assets/{index-Dg_duvDx.js.map → index-D5v9S-lB.js.map} +1 -1
  114. prefect/server/ui-v2/assets/{index-DDiyFpIV.js → index-D6GJ4go1.js} +2 -2
  115. prefect/server/ui-v2/assets/{index-DDiyFpIV.js.map → index-D6GJ4go1.js.map} +1 -1
  116. prefect/server/ui-v2/assets/{index-dSUEBAqg.js → index-DJyKqsFO.js} +2 -2
  117. prefect/server/ui-v2/assets/{index-dSUEBAqg.js.map → index-DJyKqsFO.js.map} +1 -1
  118. prefect/server/ui-v2/assets/{index-BO3SOwdV.js → index-DODEq1Pi.js} +2 -2
  119. prefect/server/ui-v2/assets/{index-BO3SOwdV.js.map → index-DODEq1Pi.js.map} +1 -1
  120. prefect/server/ui-v2/assets/{index-Dspw5HFj.js → index-DicK6p3K.js} +2 -2
  121. prefect/server/ui-v2/assets/{index-Dspw5HFj.js.map → index-DicK6p3K.js.map} +1 -1
  122. prefect/server/ui-v2/assets/{index-VOOLxiSE.js → index-DqCPbST9.js} +2 -2
  123. prefect/server/ui-v2/assets/{index-VOOLxiSE.js.map → index-DqCPbST9.js.map} +1 -1
  124. prefect/server/ui-v2/assets/{index-BTPE3vs7.js → index-DxPoKag8.js} +2 -2
  125. prefect/server/ui-v2/assets/{index-BTPE3vs7.js.map → index-DxPoKag8.js.map} +1 -1
  126. prefect/server/ui-v2/assets/{index-Wfs7Cjew.js → index-h9-QgNjZ.js} +2 -2
  127. prefect/server/ui-v2/assets/{index-Wfs7Cjew.js.map → index-h9-QgNjZ.js.map} +1 -1
  128. prefect/server/ui-v2/assets/{index-uvH5a3zO.js → index-tBdv6kBF.js} +2 -2
  129. prefect/server/ui-v2/assets/{index-uvH5a3zO.js.map → index-tBdv6kBF.js.map} +1 -1
  130. prefect/server/ui-v2/assets/{index-CgOsOj5t.js → index-udb79rgq.js} +2 -2
  131. prefect/server/ui-v2/assets/{index-CgOsOj5t.js.map → index-udb79rgq.js.map} +1 -1
  132. prefect/server/ui-v2/assets/{json-input-gXz7BuJj.js → json-input-Dt1icmrn.js} +2 -2
  133. prefect/server/ui-v2/assets/{json-input-gXz7BuJj.js.map → json-input-Dt1icmrn.js.map} +1 -1
  134. prefect/server/ui-v2/assets/{key._key-CJPbLXwU.js → key._key-Cyh5MBX_.js} +2 -2
  135. prefect/server/ui-v2/assets/{key._key-CJPbLXwU.js.map → key._key-Cyh5MBX_.js.map} +1 -1
  136. prefect/server/ui-v2/assets/{lazy-markdown-wPid80zf.js → lazy-markdown-BwIwKFRF.js} +2 -2
  137. prefect/server/ui-v2/assets/{lazy-markdown-wPid80zf.js.map → lazy-markdown-BwIwKFRF.js.map} +1 -1
  138. prefect/server/ui-v2/assets/{login-DC63bGXK.js → login-DKXFVSwk.js} +2 -2
  139. prefect/server/ui-v2/assets/{login-DC63bGXK.js.map → login-DKXFVSwk.js.map} +1 -1
  140. prefect/server/ui-v2/assets/{markdown-input-BhqrU6Eo.js → markdown-input-Dp0mBlkV.js} +2 -2
  141. prefect/server/ui-v2/assets/{markdown-input-BhqrU6Eo.js.map → markdown-input-Dp0mBlkV.js.map} +1 -1
  142. prefect/server/ui-v2/assets/{python-example-snippet-3jtXWQZN.js → python-example-snippet-BYwPjHI5.js} +3 -3
  143. prefect/server/ui-v2/assets/{python-example-snippet-3jtXWQZN.js.map → python-example-snippet-BYwPjHI5.js.map} +1 -1
  144. prefect/server/ui-v2/assets/{python-input-ZVi-v324.js → python-input-y26XMqXw.js} +2 -2
  145. prefect/server/ui-v2/assets/{python-input-ZVi-v324.js.map → python-input-y26XMqXw.js.map} +1 -1
  146. prefect/server/ui-v2/assets/{radio-group-BBMLpHGc.js → radio-group-D0van45v.js} +2 -2
  147. prefect/server/ui-v2/assets/{radio-group-BBMLpHGc.js.map → radio-group-D0van45v.js.map} +1 -1
  148. prefect/server/ui-v2/assets/{route-error-state-BJXl8qkX.js → route-error-state-CGGpuCGN.js} +2 -2
  149. prefect/server/ui-v2/assets/{route-error-state-BJXl8qkX.js.map → route-error-state-CGGpuCGN.js.map} +1 -1
  150. prefect/server/ui-v2/assets/{schema-form-BEqYjsM-.js → schema-form-2tg5SXM4.js} +2 -2
  151. prefect/server/ui-v2/assets/{schema-form-BEqYjsM-.js.map → schema-form-2tg5SXM4.js.map} +1 -1
  152. prefect/server/ui-v2/assets/{schema-form-input-string-format-datetime-l3xt3PWf.js → schema-form-input-string-format-datetime-CZt6AJ4z.js} +4 -4
  153. prefect/server/ui-v2/assets/{schema-form-input-string-format-datetime-l3xt3PWf.js.map → schema-form-input-string-format-datetime-CZt6AJ4z.js.map} +1 -1
  154. prefect/server/ui-v2/assets/{settings-txD0dR5Q.js → settings-DDUadk_N.js} +2 -2
  155. prefect/server/ui-v2/assets/{settings-txD0dR5Q.js.map → settings-DDUadk_N.js.map} +1 -1
  156. prefect/server/ui-v2/assets/{sort-filter-DHPFdKZ2.js → sort-filter-D9p3cPx9.js} +2 -2
  157. prefect/server/ui-v2/assets/{sort-filter-DHPFdKZ2.js.map → sort-filter-D9p3cPx9.js.map} +1 -1
  158. prefect/server/ui-v2/assets/{table-ULfpXJXB.js → table-vo9Do8sA.js} +2 -2
  159. prefect/server/ui-v2/assets/{table-ULfpXJXB.js.map → table-vo9Do8sA.js.map} +1 -1
  160. prefect/server/ui-v2/assets/{tags-input-BLzMOTDb.js → tags-input-Ci2JQ-k3.js} +2 -2
  161. prefect/server/ui-v2/assets/{tags-input-BLzMOTDb.js.map → tags-input-Ci2JQ-k3.js.map} +1 -1
  162. prefect/server/ui-v2/assets/{task-run-concurrency-limits-reset-dialog-BpeKHk7g.js → task-run-concurrency-limits-reset-dialog-CJzPc2gw.js} +2 -2
  163. prefect/server/ui-v2/assets/{task-run-concurrency-limits-reset-dialog-BpeKHk7g.js.map → task-run-concurrency-limits-reset-dialog-CJzPc2gw.js.map} +1 -1
  164. prefect/server/ui-v2/assets/{task-run._id-CkOl9MJs.js → task-run._id-CjevSs79.js} +2 -2
  165. prefect/server/ui-v2/assets/{task-run._id-CkOl9MJs.js.map → task-run._id-CjevSs79.js.map} +1 -1
  166. prefect/server/ui-v2/assets/{task-run._id-udkz1lhh.js → task-run._id-D8QKG5UZ.js} +2 -2
  167. prefect/server/ui-v2/assets/{task-run._id-udkz1lhh.js.map → task-run._id-D8QKG5UZ.js.map} +1 -1
  168. prefect/server/ui-v2/assets/{task-runs-pagination-B7D5K_FM.js → task-runs-pagination-CifoSGct.js} +2 -2
  169. prefect/server/ui-v2/assets/{task-runs-pagination-B7D5K_FM.js.map → task-runs-pagination-CifoSGct.js.map} +1 -1
  170. prefect/server/ui-v2/assets/{textarea-C4bdj7Jk.js → textarea-BAtfAxtV.js} +2 -2
  171. prefect/server/ui-v2/assets/{textarea-C4bdj7Jk.js.map → textarea-BAtfAxtV.js.map} +1 -1
  172. prefect/server/ui-v2/assets/{timezone-select-AdlSRQxZ.js → timezone-select-QlQTZSsF.js} +2 -2
  173. prefect/server/ui-v2/assets/{timezone-select-AdlSRQxZ.js.map → timezone-select-QlQTZSsF.js.map} +1 -1
  174. prefect/server/ui-v2/assets/{toggle-group-C-vxYz4l.js → toggle-group-BJN1vjEh.js} +2 -2
  175. prefect/server/ui-v2/assets/{toggle-group-C-vxYz4l.js.map → toggle-group-BJN1vjEh.js.map} +1 -1
  176. prefect/server/ui-v2/assets/{use-delete-automation-confirmation-dialog-Cqhaqtqe.js → use-delete-automation-confirmation-dialog-eOWJYPkD.js} +2 -2
  177. prefect/server/ui-v2/assets/{use-delete-automation-confirmation-dialog-Cqhaqtqe.js.map → use-delete-automation-confirmation-dialog-eOWJYPkD.js.map} +1 -1
  178. prefect/server/ui-v2/assets/{use-delete-block-document-confirmation-dialog-GjNhFxZe.js → use-delete-block-document-confirmation-dialog-BTwSeHRM.js} +2 -2
  179. prefect/server/ui-v2/assets/{use-delete-block-document-confirmation-dialog-GjNhFxZe.js.map → use-delete-block-document-confirmation-dialog-BTwSeHRM.js.map} +1 -1
  180. prefect/server/ui-v2/assets/{use-flow-runs-selected-rows-DfwmswyR.js → use-flow-runs-selected-rows-BL_Gv9CC.js} +2 -2
  181. prefect/server/ui-v2/assets/{use-flow-runs-selected-rows-DfwmswyR.js.map → use-flow-runs-selected-rows-BL_Gv9CC.js.map} +1 -1
  182. prefect/server/ui-v2/assets/{use-get-artifacts-flow-task-runs-BEBpG_5J.js → use-get-artifacts-flow-task-runs-TSCoomjQ.js} +2 -2
  183. prefect/server/ui-v2/assets/{use-get-artifacts-flow-task-runs-BEBpG_5J.js.map → use-get-artifacts-flow-task-runs-TSCoomjQ.js.map} +1 -1
  184. prefect/server/ui-v2/assets/{use-quick-run-BYBRcDwC.js → use-quick-run-BMnCkwSv.js} +2 -2
  185. prefect/server/ui-v2/assets/{use-quick-run-BYBRcDwC.js.map → use-quick-run-BMnCkwSv.js.map} +1 -1
  186. prefect/server/ui-v2/assets/{use-stepper-Bk97vOTm.js → use-stepper-C1wm66U2.js} +2 -2
  187. prefect/server/ui-v2/assets/{use-stepper-Bk97vOTm.js.map → use-stepper-C1wm66U2.js.map} +1 -1
  188. prefect/server/ui-v2/assets/{utilities-BQwGFLk5.js → utilities-DxRXxFOF.js} +2 -2
  189. prefect/server/ui-v2/assets/{utilities-BQwGFLk5.js.map → utilities-DxRXxFOF.js.map} +1 -1
  190. prefect/server/ui-v2/assets/{work-pool-filter-Ddhp_M-L.js → work-pool-filter-PudrkZYj.js} +2 -2
  191. prefect/server/ui-v2/assets/{work-pool-filter-Ddhp_M-L.js.map → work-pool-filter-PudrkZYj.js.map} +1 -1
  192. prefect/server/ui-v2/assets/{work-pool-queue-toggle-DX3eV3R_.js → work-pool-queue-toggle-BAOrV_0R.js} +2 -2
  193. prefect/server/ui-v2/assets/{work-pool-queue-toggle-DX3eV3R_.js.map → work-pool-queue-toggle-BAOrV_0R.js.map} +1 -1
  194. prefect/server/ui-v2/assets/{work-pool._workPoolName-BaRIsXBX.js → work-pool._workPoolName-BOM3849e.js} +2 -2
  195. prefect/server/ui-v2/assets/{work-pool._workPoolName-BaRIsXBX.js.map → work-pool._workPoolName-BOM3849e.js.map} +1 -1
  196. prefect/server/ui-v2/assets/{work-pool_._workPoolName.edit-dhS_Xz32.js → work-pool_._workPoolName.edit-CIhcG6yr.js} +2 -2
  197. prefect/server/ui-v2/assets/{work-pool_._workPoolName.edit-dhS_Xz32.js.map → work-pool_._workPoolName.edit-CIhcG6yr.js.map} +1 -1
  198. prefect/server/ui-v2/assets/{work-pool_._workPoolName.queue._workQueueName-BlstL9Se.js → work-pool_._workPoolName.queue._workQueueName-CjoM77tu.js} +2 -2
  199. prefect/server/ui-v2/assets/{work-pool_._workPoolName.queue._workQueueName-BlstL9Se.js.map → work-pool_._workPoolName.queue._workQueueName-CjoM77tu.js.map} +1 -1
  200. prefect/server/ui-v2/assets/{work-queue-icon-text-_Ez8e2dw.js → work-queue-icon-text-CP4yX3uM.js} +2 -2
  201. prefect/server/ui-v2/assets/{work-queue-icon-text-_Ez8e2dw.js.map → work-queue-icon-text-CP4yX3uM.js.map} +1 -1
  202. prefect/server/ui-v2/index.html +1 -1
  203. prefect/settings/models/flows.py +1 -14
  204. prefect/settings/models/runner.py +6 -16
  205. prefect/task_engine.py +3 -1
  206. prefect/task_worker.py +1 -1
  207. prefect/tasks.py +2 -2
  208. prefect/testing/fixtures.py +5 -23
  209. {prefect-3.6.13.dist-info → prefect-3.6.13.dev2.dist-info}/METADATA +1 -1
  210. {prefect-3.6.13.dist-info → prefect-3.6.13.dev2.dist-info}/RECORD +213 -213
  211. prefect/server/ui-v2/assets/block-type-logo-DURScH8H.js +0 -2
  212. prefect/server/ui-v2/assets/deployment_._id.duplicate-BrEOenqP.js +0 -2
  213. prefect/server/ui-v2/assets/deployment_._id.edit-BbYKPK42.js +0 -2
  214. prefect/server/ui-v2/assets/flow-run-graph-BrqoR3E2.js +0 -2
  215. prefect/server/ui-v2/assets/flow-run._id-D_wY_rBe.js +0 -4
  216. prefect/server/ui-v2/assets/flow-run._id-D_wY_rBe.js.map +0 -1
  217. prefect/server/ui-v2/assets/index-D5RdrxkU.js.map +0 -1
  218. {prefect-3.6.13.dist-info → prefect-3.6.13.dev2.dist-info}/WHEEL +0 -0
  219. {prefect-3.6.13.dist-info → prefect-3.6.13.dev2.dist-info}/entry_points.txt +0 -0
  220. {prefect-3.6.13.dist-info → prefect-3.6.13.dev2.dist-info}/licenses/LICENSE +0 -0
prefect/results.py CHANGED
@@ -33,10 +33,7 @@ from typing_extensions import ParamSpec, Self
33
33
  import prefect
34
34
  import prefect.types._datetime
35
35
  from prefect._internal.compatibility.async_dispatch import async_dispatch
36
- from prefect._internal.compatibility.blocks import (
37
- call_explicitly_async_block_method,
38
- call_explicitly_sync_block_method,
39
- )
36
+ from prefect._internal.compatibility.blocks import call_explicitly_async_block_method
40
37
  from prefect._internal.concurrency.event_loop import get_running_loop
41
38
  from prefect._result_records import R, ResultRecord, ResultRecordMetadata
42
39
  from prefect.blocks.core import Block
@@ -55,6 +52,7 @@ from prefect.serializers import Serializer
55
52
  from prefect.settings.context import get_current_settings
56
53
  from prefect.types import DateTime
57
54
  from prefect.utilities.annotations import NotSet
55
+ from prefect.utilities.asyncutils import sync_compatible
58
56
 
59
57
  if TYPE_CHECKING:
60
58
  import logging
@@ -371,7 +369,8 @@ class ResultStore(BaseModel):
371
369
  result = await store.aread(metadata.storage_key)
372
370
  return result
373
371
 
374
- async def aupdate_for_flow(self, flow: "Flow[..., Any]") -> Self:
372
+ @sync_compatible
373
+ async def update_for_flow(self, flow: "Flow[..., Any]") -> Self:
375
374
  """
376
375
  Create a new result store for a flow with updated settings.
377
376
 
@@ -394,31 +393,8 @@ class ResultStore(BaseModel):
394
393
  update["metadata_storage"] = NullFileSystem()
395
394
  return self.model_copy(update=update)
396
395
 
397
- @async_dispatch(aupdate_for_flow)
398
- def update_for_flow(self, flow: "Flow[..., Any]") -> Self:
399
- """
400
- Create a new result store for a flow with updated settings.
401
-
402
- Args:
403
- flow: The flow to update the result store for.
404
-
405
- Returns:
406
- An updated result store.
407
- """
408
- update: dict[str, Any] = {}
409
- update["cache_result_in_memory"] = flow.cache_result_in_memory
410
- if flow.result_storage is not None:
411
- update["result_storage"] = resolve_result_storage(
412
- flow.result_storage, _sync=True
413
- )
414
- if flow.result_serializer is not None:
415
- update["serializer"] = resolve_serializer(flow.result_serializer)
416
- if self.result_storage is None and update.get("result_storage") is None:
417
- update["result_storage"] = get_default_result_storage(_sync=True)
418
- update["metadata_storage"] = NullFileSystem()
419
- return self.model_copy(update=update)
420
-
421
- async def aupdate_for_task(self: Self, task: "Task[P, R]") -> Self:
396
+ @sync_compatible
397
+ async def update_for_task(self: Self, task: "Task[P, R]") -> Self:
422
398
  """
423
399
  Create a new result store for a task.
424
400
 
@@ -470,59 +446,6 @@ class ResultStore(BaseModel):
470
446
  update["metadata_storage"] = None
471
447
  return self.model_copy(update=update)
472
448
 
473
- @async_dispatch(aupdate_for_task)
474
- def update_for_task(self: Self, task: "Task[P, R]") -> Self:
475
- """
476
- Create a new result store for a task.
477
-
478
- Args:
479
- task: The task to update the result store for.
480
-
481
- Returns:
482
- An updated result store.
483
- """
484
- from prefect.transactions import get_transaction
485
-
486
- update: dict[str, Any] = {}
487
- update["cache_result_in_memory"] = task.cache_result_in_memory
488
- if task.result_storage is not None:
489
- update["result_storage"] = resolve_result_storage(
490
- task.result_storage, _sync=True
491
- )
492
- if task.result_serializer is not None:
493
- update["serializer"] = resolve_serializer(task.result_serializer)
494
- if task.result_storage_key is not None:
495
- update["storage_key_fn"] = partial(
496
- _format_user_supplied_storage_key, task.result_storage_key
497
- )
498
-
499
- # use the lock manager from a parent transaction if it exists
500
- if (current_txn := get_transaction()) and isinstance(
501
- current_txn.store, ResultStore
502
- ):
503
- update["lock_manager"] = current_txn.store.lock_manager
504
-
505
- from prefect.cache_policies import CachePolicy
506
-
507
- if isinstance(task.cache_policy, CachePolicy):
508
- if task.cache_policy.key_storage is not None:
509
- storage = task.cache_policy.key_storage
510
- if isinstance(storage, str) and not len(storage.split("/")) == 2:
511
- storage = Path(storage)
512
- update["metadata_storage"] = resolve_result_storage(storage, _sync=True)
513
- # if the cache policy has a lock manager, it takes precedence over the parent transaction
514
- if task.cache_policy.lock_manager is not None:
515
- update["lock_manager"] = task.cache_policy.lock_manager
516
-
517
- if self.result_storage is None and update.get("result_storage") is None:
518
- update["result_storage"] = get_default_result_storage(_sync=True)
519
- if (
520
- isinstance(self.metadata_storage, NullFileSystem)
521
- and update.get("metadata_storage", NotSet) is NotSet
522
- ):
523
- update["metadata_storage"] = None
524
- return self.model_copy(update=update)
525
-
526
449
  @staticmethod
527
450
  def generate_default_holder() -> str:
528
451
  """
@@ -544,7 +467,8 @@ class ResultStore(BaseModel):
544
467
  return f"{hostname}:{pid}:{thread_id}:{thread_name}:{id(current_task)}"
545
468
  return f"{hostname}:{pid}:{thread_id}:{thread_name}"
546
469
 
547
- async def _aexists(self, key: str) -> bool:
470
+ @sync_compatible
471
+ async def _exists(self, key: str) -> bool:
548
472
  """
549
473
  Check if a result record exists in storage.
550
474
 
@@ -587,49 +511,6 @@ class ResultStore(BaseModel):
587
511
  exists = True
588
512
  return exists
589
513
 
590
- def _exists(self, key: str) -> bool:
591
- """
592
- Check if a result record exists in storage.
593
-
594
- Args:
595
- key: The key to check for the existence of a result record.
596
-
597
- Returns:
598
- bool: True if the result record exists, False otherwise.
599
- """
600
- if self.metadata_storage is not None:
601
- # TODO: Add an `exists` method to commonly used storage blocks
602
- # so the entire payload doesn't need to be read
603
- try:
604
- metadata_content = call_explicitly_sync_block_method(
605
- self.metadata_storage, "read_path", (key,), {}
606
- )
607
- if metadata_content is None:
608
- return False
609
- metadata = ResultRecordMetadata.load_bytes(metadata_content)
610
-
611
- except Exception:
612
- return False
613
- else:
614
- try:
615
- content = call_explicitly_sync_block_method(
616
- self.result_storage, "read_path", (key,), {}
617
- )
618
- if content is None:
619
- return False
620
- record: ResultRecord[Any] = ResultRecord.deserialize(content)
621
- metadata = record.metadata
622
- except Exception:
623
- return False
624
-
625
- if metadata.expiration:
626
- # if the result has an expiration,
627
- # check if it is still in the future
628
- exists = metadata.expiration > prefect.types._datetime.now("UTC")
629
- else:
630
- exists = True
631
- return exists
632
-
633
514
  def exists(self, key: str) -> bool:
634
515
  """
635
516
  Check if a result record exists in storage.
@@ -640,7 +521,7 @@ class ResultStore(BaseModel):
640
521
  Returns:
641
522
  bool: True if the result record exists, False otherwise.
642
523
  """
643
- return self._exists(key=key)
524
+ return self._exists(key=key, _sync=True)
644
525
 
645
526
  async def aexists(self, key: str) -> bool:
646
527
  """
@@ -652,7 +533,7 @@ class ResultStore(BaseModel):
652
533
  Returns:
653
534
  bool: True if the result record exists, False otherwise.
654
535
  """
655
- return await self._aexists(key=key)
536
+ return await self._exists(key=key, _sync=False)
656
537
 
657
538
  def _resolved_key_path(self, key: str) -> str:
658
539
  if self.result_storage_block_id is None and (
@@ -665,7 +546,8 @@ class ResultStore(BaseModel):
665
546
  return key
666
547
  return key
667
548
 
668
- async def _aread(self, key: str, holder: str) -> "ResultRecord[Any]":
549
+ @sync_compatible
550
+ async def _read(self, key: str, holder: str) -> "ResultRecord[Any]":
669
551
  """
670
552
  Read a result record from storage.
671
553
 
@@ -728,69 +610,6 @@ class ResultStore(BaseModel):
728
610
  self.cache[resolved_key_path] = result_record
729
611
  return result_record
730
612
 
731
- def _read(self, key: str, holder: str) -> "ResultRecord[Any]":
732
- """
733
- Read a result record from storage.
734
-
735
- This is the internal implementation. Use `read` or `aread` for synchronous and
736
- asynchronous result reading respectively.
737
-
738
- Args:
739
- key: The key to read the result record from.
740
- holder: The holder of the lock if a lock was set on the record.
741
-
742
- Returns:
743
- A result record.
744
- """
745
-
746
- if self.lock_manager is not None and not self.is_lock_holder(key, holder):
747
- self.wait_for_lock(key)
748
-
749
- resolved_key_path = self._resolved_key_path(key)
750
-
751
- if resolved_key_path in self.cache:
752
- return self.cache[resolved_key_path]
753
-
754
- if self.result_storage is None:
755
- self.result_storage = get_default_result_storage(_sync=True)
756
-
757
- if self.metadata_storage is not None:
758
- metadata_content = call_explicitly_sync_block_method(
759
- self.metadata_storage,
760
- "read_path",
761
- (key,),
762
- {},
763
- )
764
- metadata = ResultRecordMetadata.load_bytes(metadata_content)
765
- assert metadata.storage_key is not None, (
766
- "Did not find storage key in metadata"
767
- )
768
- result_content = call_explicitly_sync_block_method(
769
- self.result_storage,
770
- "read_path",
771
- (metadata.storage_key,),
772
- {},
773
- )
774
- result_record: ResultRecord[Any] = (
775
- ResultRecord.deserialize_from_result_and_metadata(
776
- result=result_content, metadata=metadata_content
777
- )
778
- )
779
- else:
780
- content = call_explicitly_sync_block_method(
781
- self.result_storage,
782
- "read_path",
783
- (key,),
784
- {},
785
- )
786
- result_record: ResultRecord[Any] = ResultRecord.deserialize(
787
- content, backup_serializer=self.serializer
788
- )
789
-
790
- if self.cache_result_in_memory:
791
- self.cache[resolved_key_path] = result_record
792
- return result_record
793
-
794
613
  def read(
795
614
  self,
796
615
  key: str,
@@ -807,7 +626,7 @@ class ResultStore(BaseModel):
807
626
  A result record.
808
627
  """
809
628
  holder = holder or self.generate_default_holder()
810
- return self._read(key=key, holder=holder)
629
+ return self._read(key=key, holder=holder, _sync=True)
811
630
 
812
631
  async def aread(
813
632
  self,
@@ -825,7 +644,7 @@ class ResultStore(BaseModel):
825
644
  A result record.
826
645
  """
827
646
  holder = holder or self.generate_default_holder()
828
- return await self._aread(key=key, holder=holder)
647
+ return await self._read(key=key, holder=holder, _sync=False)
829
648
 
830
649
  def create_result_record(
831
650
  self,
@@ -915,7 +734,8 @@ class ResultStore(BaseModel):
915
734
  holder=holder,
916
735
  )
917
736
 
918
- async def _apersist_result_record(
737
+ @sync_compatible
738
+ async def _persist_result_record(
919
739
  self, result_record: "ResultRecord[Any]", holder: str
920
740
  ) -> None:
921
741
  """
@@ -978,69 +798,6 @@ class ResultStore(BaseModel):
978
798
  if self.cache_result_in_memory:
979
799
  self.cache[key] = result_record
980
800
 
981
- def _persist_result_record(
982
- self, result_record: "ResultRecord[Any]", holder: str
983
- ) -> None:
984
- """
985
- Persist a result record to storage.
986
-
987
- Args:
988
- result_record: The result record to persist.
989
- holder: The holder of the lock if a lock was set on the record.
990
- """
991
- assert result_record.metadata.storage_key is not None, (
992
- "Storage key is required on result record"
993
- )
994
-
995
- key = result_record.metadata.storage_key
996
- if result_record.metadata.storage_block_id is None:
997
- basepath = (
998
- _resolve_path("")
999
- if (
1000
- _resolve_path := getattr(self.result_storage, "_resolve_path", None)
1001
- )
1002
- else Path(".").resolve()
1003
- )
1004
- base_key = key if basepath is None else str(Path(key).relative_to(basepath))
1005
- else:
1006
- base_key = key
1007
- if (
1008
- self.lock_manager is not None
1009
- and self.is_locked(base_key)
1010
- and not self.is_lock_holder(base_key, holder)
1011
- ):
1012
- raise RuntimeError(
1013
- f"Cannot write to result record with key {base_key} because it is locked by "
1014
- f"another holder."
1015
- )
1016
- if self.result_storage is None:
1017
- self.result_storage = get_default_result_storage(_sync=True)
1018
-
1019
- # If metadata storage is configured, write result and metadata separately
1020
- if self.metadata_storage is not None:
1021
- call_explicitly_sync_block_method(
1022
- self.result_storage,
1023
- "write_path",
1024
- (result_record.metadata.storage_key,),
1025
- {"content": result_record.serialize_result()},
1026
- )
1027
- call_explicitly_sync_block_method(
1028
- self.metadata_storage,
1029
- "write_path",
1030
- (base_key,),
1031
- {"content": result_record.serialize_metadata()},
1032
- )
1033
- # Otherwise, write the result metadata and result together
1034
- else:
1035
- call_explicitly_sync_block_method(
1036
- self.result_storage,
1037
- "write_path",
1038
- (result_record.metadata.storage_key,),
1039
- {"content": result_record.serialize()},
1040
- )
1041
- if self.cache_result_in_memory:
1042
- self.cache[key] = result_record
1043
-
1044
801
  def persist_result_record(
1045
802
  self, result_record: "ResultRecord[Any]", holder: str | None = None
1046
803
  ) -> None:
@@ -1051,7 +808,9 @@ class ResultStore(BaseModel):
1051
808
  result_record: The result record to persist.
1052
809
  """
1053
810
  holder = holder or self.generate_default_holder()
1054
- return self._persist_result_record(result_record=result_record, holder=holder)
811
+ return self._persist_result_record(
812
+ result_record=result_record, holder=holder, _sync=True
813
+ )
1055
814
 
1056
815
  async def apersist_result_record(
1057
816
  self, result_record: "ResultRecord[Any]", holder: str | None = None
@@ -1063,8 +822,8 @@ class ResultStore(BaseModel):
1063
822
  result_record: The result record to persist.
1064
823
  """
1065
824
  holder = holder or self.generate_default_holder()
1066
- return await self._apersist_result_record(
1067
- result_record=result_record, holder=holder
825
+ return await self._persist_result_record(
826
+ result_record=result_record, holder=holder, _sync=False
1068
827
  )
1069
828
 
1070
829
  def supports_isolation_level(self, level: "IsolationLevel") -> bool:
prefect/runner/runner.py CHANGED
@@ -56,6 +56,7 @@ from typing import (
56
56
  TYPE_CHECKING,
57
57
  Any,
58
58
  Callable,
59
+ Coroutine,
59
60
  Dict,
60
61
  Iterable,
61
62
  List,
@@ -166,10 +167,9 @@ class Runner:
166
167
  query_seconds: The number of seconds to wait between querying for
167
168
  scheduled flow runs; defaults to `PREFECT_RUNNER_POLL_FREQUENCY`
168
169
  prefetch_seconds: The number of seconds to prefetch flow runs for.
169
- heartbeat_seconds: The number of seconds between heartbeat events emitted
170
- by flow runs managed by this runner. If not provided, the value of
171
- `PREFECT_FLOWS_HEARTBEAT_FREQUENCY` will be used. Heartbeats are used
172
- to detect crashed flow runs.
170
+ heartbeat_seconds: The number of seconds to wait between emitting
171
+ flow run heartbeats. The runner will not emit heartbeats if the value is None.
172
+ Defaults to `PREFECT_RUNNER_HEARTBEAT_FREQUENCY`.
173
173
  limit: The maximum number of flow runs this runner should be running at. Provide `None` for no limit.
174
174
  If not provided, the runner will use the value of `PREFECT_RUNNER_PROCESS_LIMIT`.
175
175
  pause_on_shutdown: A boolean for whether or not to automatically pause
@@ -202,8 +202,6 @@ class Runner:
202
202
  asyncio.run(runner.start())
203
203
  ```
204
204
  """
205
- self._heartbeat_seconds = heartbeat_seconds
206
-
207
205
  settings = get_current_settings()
208
206
 
209
207
  if name and ("/" in name or "%" in name):
@@ -223,6 +221,12 @@ class Runner:
223
221
 
224
222
  self.query_seconds: float = query_seconds or settings.runner.poll_frequency
225
223
  self._prefetch_seconds: float = prefetch_seconds
224
+ self.heartbeat_seconds: float | None = (
225
+ heartbeat_seconds or settings.runner.heartbeat_frequency
226
+ )
227
+ if self.heartbeat_seconds is not None and self.heartbeat_seconds < 30:
228
+ raise ValueError("Heartbeat must be 30 seconds or greater.")
229
+ self._heartbeat_task: asyncio.Task[None] | None = None
226
230
  self._events_client: EventsClient = get_events_client(checkpoint_every=1)
227
231
 
228
232
  self._exit_stack = AsyncExitStack()
@@ -640,6 +644,9 @@ class Runner:
640
644
 
641
645
  task_status.started(process.pid)
642
646
 
647
+ if self.heartbeat_seconds is not None:
648
+ await self._emit_flow_run_heartbeat(flow_run)
649
+
643
650
  # Only add the process to the map if it is still running
644
651
  # The process may be a multiprocessing.context.SpawnProcess, in which case it will have an `exitcode`` attribute
645
652
  # but no `returncode` attribute
@@ -675,11 +682,6 @@ class Runner:
675
682
 
676
683
  flow_run = FlowRun.model_validate(bundle["flow_run"])
677
684
 
678
- # Add heartbeat_seconds to env if configured
679
- if self._heartbeat_seconds is not None:
680
- env = env or {}
681
- env["PREFECT_FLOWS_HEARTBEAT_FREQUENCY"] = str(int(self._heartbeat_seconds))
682
-
683
685
  async with context:
684
686
  if not self._acquire_limit_slot(flow_run.id):
685
687
  return
@@ -693,6 +695,9 @@ class Runner:
693
695
  await self._propose_crashed_state(flow_run, msg)
694
696
  raise RuntimeError(msg)
695
697
 
698
+ if self.heartbeat_seconds is not None:
699
+ await self._emit_flow_run_heartbeat(flow_run)
700
+
696
701
  await self._add_flow_run_process_map_entry(
697
702
  flow_run.id, ProcessMapEntry(pid=process.pid, flow_run=flow_run)
698
703
  )
@@ -791,14 +796,7 @@ class Runner:
791
796
  if flow_run.deployment_id is not None:
792
797
  flow = self._deployment_flow_map.get(flow_run.deployment_id)
793
798
  if flow:
794
- subprocess_env: dict[str, str] = {}
795
- if self._heartbeat_seconds is not None:
796
- subprocess_env["PREFECT_FLOWS_HEARTBEAT_FREQUENCY"] = str(
797
- int(self._heartbeat_seconds)
798
- )
799
- process = run_flow_in_subprocess(
800
- flow, flow_run=flow_run, env=subprocess_env or None
801
- )
799
+ process = run_flow_in_subprocess(flow, flow_run=flow_run)
802
800
  task_status.started(process)
803
801
  await anyio.to_thread.run_sync(process.join)
804
802
  return process.exitcode
@@ -831,15 +829,6 @@ class Runner:
831
829
  "PREFECT__ENABLE_CANCELLATION_AND_CRASHED_HOOKS": "false",
832
830
  },
833
831
  **({"PREFECT__FLOW_ENTRYPOINT": entrypoint} if entrypoint else {}),
834
- **(
835
- {
836
- "PREFECT_FLOWS_HEARTBEAT_FREQUENCY": str(
837
- int(self._heartbeat_seconds)
838
- )
839
- }
840
- if self._heartbeat_seconds is not None
841
- else {}
842
- ),
843
832
  }
844
833
  )
845
834
  env.update(**os.environ) # is this really necessary??
@@ -1060,6 +1049,51 @@ class Runner:
1060
1049
  flow = None
1061
1050
  return flow, deployment
1062
1051
 
1052
+ async def _emit_flow_run_heartbeats(self):
1053
+ coros: list[Coroutine[Any, Any, Any]] = []
1054
+ for entry in self._flow_run_process_map.values():
1055
+ coros.append(self._emit_flow_run_heartbeat(entry["flow_run"]))
1056
+ await asyncio.gather(*coros)
1057
+
1058
+ async def _emit_flow_run_heartbeat(self, flow_run: "FlowRun"):
1059
+ from prefect import __version__
1060
+
1061
+ related: list[RelatedResource] = []
1062
+ tags: list[str] = []
1063
+
1064
+ flow, deployment = await self._get_flow_and_deployment(flow_run)
1065
+ if deployment:
1066
+ related.append(deployment.as_related_resource())
1067
+ tags.extend(deployment.tags)
1068
+ if flow:
1069
+ related.append(
1070
+ RelatedResource(
1071
+ {
1072
+ "prefect.resource.id": f"prefect.flow.{flow.id}",
1073
+ "prefect.resource.role": "flow",
1074
+ "prefect.resource.name": flow.name,
1075
+ }
1076
+ )
1077
+ )
1078
+ tags.extend(flow_run.tags)
1079
+
1080
+ related = [RelatedResource.model_validate(r) for r in related]
1081
+ related += tags_as_related_resources(set(tags))
1082
+
1083
+ await self._events_client.emit(
1084
+ Event(
1085
+ event="prefect.flow-run.heartbeat",
1086
+ resource=Resource(
1087
+ {
1088
+ "prefect.resource.id": f"prefect.flow-run.{flow_run.id}",
1089
+ "prefect.resource.name": flow_run.name,
1090
+ "prefect.version": __version__,
1091
+ }
1092
+ ),
1093
+ related=related,
1094
+ )
1095
+ )
1096
+
1063
1097
  def _event_resource(self):
1064
1098
  from prefect import __version__
1065
1099
 
@@ -1111,7 +1145,7 @@ class Runner:
1111
1145
  related=related,
1112
1146
  )
1113
1147
  )
1114
- self._logger.debug(f"Emitted cancelled-flow-run event for {flow_run.id}")
1148
+ self._logger.debug(f"Emitted flow run heartbeat event for {flow_run.id}")
1115
1149
 
1116
1150
  async def _get_scheduled_flow_runs(
1117
1151
  self,
@@ -1255,6 +1289,9 @@ class Runner:
1255
1289
  flow_run.id,
1256
1290
  ProcessMapEntry(pid=readiness_result.pid, flow_run=flow_run),
1257
1291
  )
1292
+ # Heartbeats are opt-in and only emitted if a heartbeat frequency is set
1293
+ if self.heartbeat_seconds is not None:
1294
+ await self._emit_flow_run_heartbeat(flow_run)
1258
1295
 
1259
1296
  run_logger.info(f"Completed submission of flow run '{flow_run.id}'")
1260
1297
  else:
@@ -1568,6 +1605,15 @@ class Runner:
1568
1605
  if not hasattr(self, "_loops_task_group") or not self._loops_task_group:
1569
1606
  self._loops_task_group: anyio.abc.TaskGroup = anyio.create_task_group()
1570
1607
 
1608
+ if self.heartbeat_seconds is not None:
1609
+ self._heartbeat_task = asyncio.create_task(
1610
+ critical_service_loop(
1611
+ workload=self._emit_flow_run_heartbeats,
1612
+ interval=self.heartbeat_seconds,
1613
+ jitter_range=0.3,
1614
+ )
1615
+ )
1616
+
1571
1617
  self.started = True
1572
1618
  return self
1573
1619
 
@@ -1586,6 +1632,13 @@ class Runner:
1586
1632
  shutil.rmtree(str(self._tmp_dir), ignore_errors=True)
1587
1633
  del self._runs_task_group, self._loops_task_group
1588
1634
 
1635
+ if self._heartbeat_task:
1636
+ self._heartbeat_task.cancel()
1637
+ try:
1638
+ await self._heartbeat_task
1639
+ except asyncio.CancelledError:
1640
+ pass
1641
+
1589
1642
  def __repr__(self) -> str:
1590
1643
  return f"Runner(name={self.name!r})"
1591
1644
 
prefect/runner/storage.py CHANGED
@@ -346,12 +346,11 @@ class GitRepository:
346
346
  )
347
347
  existing_repo_url = None
348
348
  existing_repo_url = strip_auth_from_url(result.stdout.decode().strip())
349
- configured_repo_url = strip_auth_from_url(self._url)
350
349
 
351
- if existing_repo_url != configured_repo_url:
350
+ if existing_repo_url != self._url:
352
351
  raise ValueError(
353
352
  f"The existing repository at {str(self.destination)} "
354
- f"does not match the configured repository {configured_repo_url}"
353
+ f"does not match the configured repository {self._url}"
355
354
  )
356
355
 
357
356
  # Sparsely checkout the repository if directories are specified and the repo is not in sparse-checkout mode already
@@ -645,45 +645,6 @@ class FlowRunFilterIdempotencyKey(PrefectFilterBaseModel):
645
645
  return filters
646
646
 
647
647
 
648
- class FlowRunFilterCreatedBy(PrefectOperatorFilterBaseModel):
649
- """Filter by `FlowRun.created_by`."""
650
-
651
- id_: Optional[list[UUID]] = Field(
652
- default=None,
653
- description="A list of creator IDs to include",
654
- )
655
- type_: Optional[list[str]] = Field(
656
- default=None,
657
- description=(
658
- "A list of creator types to include. For example, 'DEPLOYMENT' for "
659
- "scheduled runs or 'AUTOMATION' for runs triggered by automations."
660
- ),
661
- examples=[["DEPLOYMENT", "AUTOMATION"]],
662
- )
663
- is_null_: Optional[bool] = Field(
664
- default=None,
665
- description="If true, only include flow runs without a creator",
666
- )
667
-
668
- def _get_filter_list(
669
- self, db: "PrefectDBInterface"
670
- ) -> Iterable[sa.ColumnExpressionArgument[bool]]:
671
- filters: list[sa.ColumnExpressionArgument[bool]] = []
672
- if self.id_ is not None:
673
- # JSON stores UUIDs as strings, use astext for text extraction
674
- id_strings = [str(id_val) for id_val in self.id_]
675
- filters.append(db.FlowRun.created_by["id"].astext.in_(id_strings))
676
- if self.type_ is not None:
677
- filters.append(db.FlowRun.created_by["type"].astext.in_(self.type_))
678
- if self.is_null_ is not None:
679
- filters.append(
680
- db.FlowRun.created_by.is_(None)
681
- if self.is_null_
682
- else db.FlowRun.created_by.is_not(None)
683
- )
684
- return filters
685
-
686
-
687
648
  class FlowRunFilter(PrefectOperatorFilterBaseModel):
688
649
  """Filter flow runs. Only flow runs matching all criteria will be returned"""
689
650
 
@@ -730,9 +691,6 @@ class FlowRunFilter(PrefectOperatorFilterBaseModel):
730
691
  idempotency_key: Optional[FlowRunFilterIdempotencyKey] = Field(
731
692
  default=None, description="Filter criteria for `FlowRun.idempotency_key`"
732
693
  )
733
- created_by: Optional[FlowRunFilterCreatedBy] = Field(
734
- default=None, description="Filter criteria for `FlowRun.created_by`"
735
- )
736
694
 
737
695
  def only_filters_on_id(self) -> bool:
738
696
  return bool(
@@ -751,7 +709,6 @@ class FlowRunFilter(PrefectOperatorFilterBaseModel):
751
709
  and self.parent_flow_run_id is None
752
710
  and self.parent_task_run_id is None
753
711
  and self.idempotency_key is None
754
- and self.created_by is None
755
712
  )
756
713
 
757
714
  def _get_filter_list(
@@ -787,8 +744,6 @@ class FlowRunFilter(PrefectOperatorFilterBaseModel):
787
744
  filters.append(self.parent_task_run_id.as_sql_filter())
788
745
  if self.idempotency_key is not None:
789
746
  filters.append(self.idempotency_key.as_sql_filter())
790
- if self.created_by is not None:
791
- filters.append(self.created_by.as_sql_filter())
792
747
 
793
748
  return filters
794
749