prefect 3.6.13.dev2__py3-none-any.whl → 3.6.14.dev1__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 (226) hide show
  1. prefect/_build_info.py +3 -3
  2. prefect/_internal/compatibility/blocks.py +18 -0
  3. prefect/client/schemas/filters.py +24 -0
  4. prefect/flow_engine.py +192 -10
  5. prefect/flows.py +61 -2
  6. prefect/results.py +262 -21
  7. prefect/runner/runner.py +29 -82
  8. prefect/runner/storage.py +3 -2
  9. prefect/server/schemas/filters.py +45 -0
  10. prefect/server/ui-v2/assets/{artifact-card-C8JEQRHl.js → artifact-card-DkgEVahF.js} +2 -2
  11. prefect/server/ui-v2/assets/{artifact-card-C8JEQRHl.js.map → artifact-card-DkgEVahF.js.map} +1 -1
  12. prefect/server/ui-v2/assets/{artifact._id-67V8kTg9.js → artifact._id-C7yJhkKY.js} +2 -2
  13. prefect/server/ui-v2/assets/{artifact._id-67V8kTg9.js.map → artifact._id-C7yJhkKY.js.map} +1 -1
  14. prefect/server/ui-v2/assets/{automation-wizard-DuxZ47Nn.js → automation-wizard-BLI4sqs9.js} +2 -2
  15. prefect/server/ui-v2/assets/{automation-wizard-DuxZ47Nn.js.map → automation-wizard-BLI4sqs9.js.map} +1 -1
  16. prefect/server/ui-v2/assets/{automation._id-D8S8r4Ji.js → automation._id-OLes3du9.js} +2 -2
  17. prefect/server/ui-v2/assets/{automation._id-D8S8r4Ji.js.map → automation._id-OLes3du9.js.map} +1 -1
  18. prefect/server/ui-v2/assets/{automation_._id.edit-C1fOM0Hx.js → automation_._id.edit-QrSzkDDB.js} +2 -2
  19. prefect/server/ui-v2/assets/{automation_._id.edit-C1fOM0Hx.js.map → automation_._id.edit-QrSzkDDB.js.map} +1 -1
  20. prefect/server/ui-v2/assets/{automations-header-DrijTNGi.js → automations-header-BoJc9HK8.js} +2 -2
  21. prefect/server/ui-v2/assets/{automations-header-DrijTNGi.js.map → automations-header-BoJc9HK8.js.map} +1 -1
  22. prefect/server/ui-v2/assets/{base-job-template-form-section-BDkqOkpe.js → base-job-template-form-section-Br6s0jNS.js} +2 -2
  23. prefect/server/ui-v2/assets/{base-job-template-form-section-BDkqOkpe.js.map → base-job-template-form-section-Br6s0jNS.js.map} +1 -1
  24. prefect/server/ui-v2/assets/{block-type-details-CrFHWH5D.js → block-type-details-BSbBP-Nm.js} +2 -2
  25. prefect/server/ui-v2/assets/{block-type-details-CrFHWH5D.js.map → block-type-details-BSbBP-Nm.js.map} +1 -1
  26. prefect/server/ui-v2/assets/block-type-logo-DNFERTyH.js +2 -0
  27. prefect/server/ui-v2/assets/{block-type-logo-WQNm0PzP.js.map → block-type-logo-DNFERTyH.js.map} +1 -1
  28. prefect/server/ui-v2/assets/{block._id-C3gN1Ae8.js → block._id-DO6Hulrv.js} +2 -2
  29. prefect/server/ui-v2/assets/{block._id-C3gN1Ae8.js.map → block._id-DO6Hulrv.js.map} +1 -1
  30. prefect/server/ui-v2/assets/{block_._id.edit-DzPuSvat.js → block_._id.edit-BWF0TQvv.js} +2 -2
  31. prefect/server/ui-v2/assets/{block_._id.edit-DzPuSvat.js.map → block_._id.edit-BWF0TQvv.js.map} +1 -1
  32. prefect/server/ui-v2/assets/{catalog-T-7MqsyE.js → catalog-BlaqZP0r.js} +2 -2
  33. prefect/server/ui-v2/assets/{catalog-T-7MqsyE.js.map → catalog-BlaqZP0r.js.map} +1 -1
  34. prefect/server/ui-v2/assets/{catalog_._slug-DMOI61fc.js → catalog_._slug-BZWiMtjq.js} +2 -2
  35. prefect/server/ui-v2/assets/{catalog_._slug-DMOI61fc.js.map → catalog_._slug-BZWiMtjq.js.map} +1 -1
  36. prefect/server/ui-v2/assets/{catalog_._slug_.create-l9bGUPi-.js → catalog_._slug_.create-oHffbeMs.js} +2 -2
  37. prefect/server/ui-v2/assets/{catalog_._slug_.create-l9bGUPi-.js.map → catalog_._slug_.create-oHffbeMs.js.map} +1 -1
  38. prefect/server/ui-v2/assets/{collapsible-DM-Ze5-Y.js → collapsible-D45P93xU.js} +2 -2
  39. prefect/server/ui-v2/assets/{collapsible-DM-Ze5-Y.js.map → collapsible-D45P93xU.js.map} +1 -1
  40. prefect/server/ui-v2/assets/{concurrency-limit._id-BHEt9viF.js → concurrency-limit._id-B6oyPVya.js} +2 -2
  41. prefect/server/ui-v2/assets/{concurrency-limit._id-BHEt9viF.js.map → concurrency-limit._id-B6oyPVya.js.map} +1 -1
  42. prefect/server/ui-v2/assets/{create-BMxUOyN5.js → create-CcYMd6w6.js} +2 -2
  43. prefect/server/ui-v2/assets/{create-BMxUOyN5.js.map → create-CcYMd6w6.js.map} +1 -1
  44. prefect/server/ui-v2/assets/{create-Cq-EAHVk.js → create-DK1WvDhf.js} +2 -2
  45. prefect/server/ui-v2/assets/{create-Cq-EAHVk.js.map → create-DK1WvDhf.js.map} +1 -1
  46. prefect/server/ui-v2/assets/{data-table-Bw8TZx9D.js → data-table-B_iyrrTm.js} +2 -2
  47. prefect/server/ui-v2/assets/{data-table-Bw8TZx9D.js.map → data-table-B_iyrrTm.js.map} +1 -1
  48. prefect/server/ui-v2/assets/{delete-confirmation-dialog-CTdWZlGM.js → delete-confirmation-dialog-7GKqeKy1.js} +2 -2
  49. prefect/server/ui-v2/assets/{delete-confirmation-dialog-CTdWZlGM.js.map → delete-confirmation-dialog-7GKqeKy1.js.map} +1 -1
  50. prefect/server/ui-v2/assets/{deployment-action-header-BObYqmI_.js → deployment-action-header-DrvnfE9Y.js} +2 -2
  51. prefect/server/ui-v2/assets/{deployment-action-header-BObYqmI_.js.map → deployment-action-header-DrvnfE9Y.js.map} +1 -1
  52. prefect/server/ui-v2/assets/{deployment-form-CntyhRrK.js → deployment-form-DsAij22s.js} +3 -3
  53. prefect/server/ui-v2/assets/{deployment-form-CntyhRrK.js.map → deployment-form-DsAij22s.js.map} +1 -1
  54. prefect/server/ui-v2/assets/{deployment-links-DBUSCaxB.js → deployment-links-BwqfKWQX.js} +2 -2
  55. prefect/server/ui-v2/assets/{deployment-links-DBUSCaxB.js.map → deployment-links-BwqfKWQX.js.map} +1 -1
  56. prefect/server/ui-v2/assets/{deployment._id-BXanVFA8.js → deployment._id-E5e5s6bO.js} +2 -2
  57. prefect/server/ui-v2/assets/{deployment._id-BXanVFA8.js.map → deployment._id-E5e5s6bO.js.map} +1 -1
  58. prefect/server/ui-v2/assets/{deployment._id-Q5cvHMX9.js → deployment._id-U6KrtWw0.js} +2 -2
  59. prefect/server/ui-v2/assets/{deployment._id-Q5cvHMX9.js.map → deployment._id-U6KrtWw0.js.map} +1 -1
  60. prefect/server/ui-v2/assets/deployment_._id.duplicate-C_kNyCA9.js +2 -0
  61. prefect/server/ui-v2/assets/{deployment_._id.duplicate-DB-4hHHc.js.map → deployment_._id.duplicate-C_kNyCA9.js.map} +1 -1
  62. prefect/server/ui-v2/assets/deployment_._id.edit-BP8pfTOR.js +2 -0
  63. prefect/server/ui-v2/assets/{deployment_._id.edit-CWf1RIGy.js.map → deployment_._id.edit-BP8pfTOR.js.map} +1 -1
  64. prefect/server/ui-v2/assets/{deployment_._id.run-CoHGXFoM.js → deployment_._id.run-Di3-4Mtk.js} +2 -2
  65. prefect/server/ui-v2/assets/{deployment_._id.run-CoHGXFoM.js.map → deployment_._id.run-Di3-4Mtk.js.map} +1 -1
  66. prefect/server/ui-v2/assets/{dropdown-menu-CT-s-V7d.js → dropdown-menu-iS8iJMpx.js} +2 -2
  67. prefect/server/ui-v2/assets/{dropdown-menu-CT-s-V7d.js.map → dropdown-menu-iS8iJMpx.js.map} +1 -1
  68. prefect/server/ui-v2/assets/{event._eventDate._eventId-BITo_GYL.js → event._eventDate._eventId-CG15qDSZ.js} +2 -2
  69. prefect/server/ui-v2/assets/{event._eventDate._eventId-BITo_GYL.js.map → event._eventDate._eventId-CG15qDSZ.js.map} +1 -1
  70. prefect/server/ui-v2/assets/flow-run-graph-B1vF1w0Z.js +2 -0
  71. prefect/server/ui-v2/assets/flow-run-graph-B1vF1w0Z.js.map +1 -0
  72. prefect/server/ui-v2/assets/flow-run._id-BHY61H6u.js +4 -0
  73. prefect/server/ui-v2/assets/flow-run._id-BHY61H6u.js.map +1 -0
  74. prefect/server/ui-v2/assets/{flow-run._id-BtSgRDtA.js → flow-run._id-D_eAHZ5z.js} +2 -2
  75. prefect/server/ui-v2/assets/{flow-run._id-BtSgRDtA.js.map → flow-run._id-D_eAHZ5z.js.map} +1 -1
  76. prefect/server/ui-v2/assets/{flow-run._id-Bbm9OpDi.js → flow-run._id-wco0Q19f.js} +2 -2
  77. prefect/server/ui-v2/assets/{flow-run._id-Bbm9OpDi.js.map → flow-run._id-wco0Q19f.js.map} +1 -1
  78. prefect/server/ui-v2/assets/{flow-runs-pagination-LrU9Aio8.js → flow-runs-pagination-DzGHOht3.js} +2 -2
  79. prefect/server/ui-v2/assets/{flow-runs-pagination-LrU9Aio8.js.map → flow-runs-pagination-DzGHOht3.js.map} +1 -1
  80. prefect/server/ui-v2/assets/{flow._id-BJBRokk4.js → flow._id-Dn2so3rK.js} +2 -2
  81. prefect/server/ui-v2/assets/{flow._id-BJBRokk4.js.map → flow._id-Dn2so3rK.js.map} +1 -1
  82. prefect/server/ui-v2/assets/{form-CVSlEnl8.js → form-rC8pxeRl.js} +2 -2
  83. prefect/server/ui-v2/assets/{form-CVSlEnl8.js.map → form-rC8pxeRl.js.map} +1 -1
  84. prefect/server/ui-v2/assets/{header-4plZZheZ.js → header-C0zV3WDn.js} +2 -2
  85. prefect/server/ui-v2/assets/{header-4plZZheZ.js.map → header-C0zV3WDn.js.map} +1 -1
  86. prefect/server/ui-v2/assets/{header-D3uM8_xM.js → header-C6ko3O7G.js} +2 -2
  87. prefect/server/ui-v2/assets/{header-D3uM8_xM.js.map → header-C6ko3O7G.js.map} +1 -1
  88. prefect/server/ui-v2/assets/{header-9rXZ4r39.js → header-CuAwmJ64.js} +2 -2
  89. prefect/server/ui-v2/assets/{header-9rXZ4r39.js.map → header-CuAwmJ64.js.map} +1 -1
  90. prefect/server/ui-v2/assets/{index-6OsEYhIi.js → index-0g8aAM30.js} +2 -2
  91. prefect/server/ui-v2/assets/{index-6OsEYhIi.js.map → index-0g8aAM30.js.map} +1 -1
  92. prefect/server/ui-v2/assets/{index-BeoNC2tJ.js → index-BIqzfifB.js} +2 -2
  93. prefect/server/ui-v2/assets/{index-BeoNC2tJ.js.map → index-BIqzfifB.js.map} +1 -1
  94. prefect/server/ui-v2/assets/{index-ChIrfjIW.js → index-BKbBu9gL.js} +2 -2
  95. prefect/server/ui-v2/assets/{index-ChIrfjIW.js.map → index-BKbBu9gL.js.map} +1 -1
  96. prefect/server/ui-v2/assets/{index-h9-QgNjZ.js → index-BYQ1P2TC.js} +2 -2
  97. prefect/server/ui-v2/assets/{index-h9-QgNjZ.js.map → index-BYQ1P2TC.js.map} +1 -1
  98. prefect/server/ui-v2/assets/{index-DODEq1Pi.js → index-BdgomKuA.js} +2 -2
  99. prefect/server/ui-v2/assets/{index-DODEq1Pi.js.map → index-BdgomKuA.js.map} +1 -1
  100. prefect/server/ui-v2/assets/{index-DqCPbST9.js → index-Benf9xn3.js} +2 -2
  101. prefect/server/ui-v2/assets/{index-DqCPbST9.js.map → index-Benf9xn3.js.map} +1 -1
  102. prefect/server/ui-v2/assets/{index-B7xsJ-QH.js → index-BjsWjEUO.js} +2 -2
  103. prefect/server/ui-v2/assets/{index-B7xsJ-QH.js.map → index-BjsWjEUO.js.map} +1 -1
  104. prefect/server/ui-v2/assets/{index-DicK6p3K.js → index-C3D0Re7U.js} +2 -2
  105. prefect/server/ui-v2/assets/{index-DicK6p3K.js.map → index-C3D0Re7U.js.map} +1 -1
  106. prefect/server/ui-v2/assets/{index-BlpD74iH.js → index-CCs20W5W.js} +2 -2
  107. prefect/server/ui-v2/assets/{index-BlpD74iH.js.map → index-CCs20W5W.js.map} +1 -1
  108. prefect/server/ui-v2/assets/{index-udb79rgq.js → index-CEStyUb-.js} +2 -2
  109. prefect/server/ui-v2/assets/{index-udb79rgq.js.map → index-CEStyUb-.js.map} +1 -1
  110. prefect/server/ui-v2/assets/{index-tBdv6kBF.js → index-CYpFLd7y.js} +2 -2
  111. prefect/server/ui-v2/assets/{index-tBdv6kBF.js.map → index-CYpFLd7y.js.map} +1 -1
  112. prefect/server/ui-v2/assets/{index-CDyLkbVG.js → index-CcVSC2pV.js} +2 -2
  113. prefect/server/ui-v2/assets/{index-CDyLkbVG.js.map → index-CcVSC2pV.js.map} +1 -1
  114. prefect/server/ui-v2/assets/index-Cd7pM9pk.js +17 -0
  115. prefect/server/ui-v2/assets/index-Cd7pM9pk.js.map +1 -0
  116. prefect/server/ui-v2/assets/{index-DJyKqsFO.js → index-CotXErTe.js} +2 -2
  117. prefect/server/ui-v2/assets/{index-DJyKqsFO.js.map → index-CotXErTe.js.map} +1 -1
  118. prefect/server/ui-v2/assets/{index-DxPoKag8.js → index-D8fZaZa7.js} +2 -2
  119. prefect/server/ui-v2/assets/{index-DxPoKag8.js.map → index-D8fZaZa7.js.map} +1 -1
  120. prefect/server/ui-v2/assets/{index-D6GJ4go1.js → index-DB2qeheX.js} +2 -2
  121. prefect/server/ui-v2/assets/{index-D6GJ4go1.js.map → index-DB2qeheX.js.map} +1 -1
  122. prefect/server/ui-v2/assets/{index-CzCSgCK-.js → index-DvKYj8ko.js} +2 -2
  123. prefect/server/ui-v2/assets/{index-CzCSgCK-.js.map → index-DvKYj8ko.js.map} +1 -1
  124. prefect/server/ui-v2/assets/{index-BfJLUM7N.js → index-DwkedbVy.js} +2 -2
  125. prefect/server/ui-v2/assets/{index-BfJLUM7N.js.map → index-DwkedbVy.js.map} +1 -1
  126. prefect/server/ui-v2/assets/index-b9WBW430.css +1 -0
  127. prefect/server/ui-v2/assets/{index-CDwJvjUM.js → index-fhrsIceD.js} +2 -2
  128. prefect/server/ui-v2/assets/{index-CDwJvjUM.js.map → index-fhrsIceD.js.map} +1 -1
  129. prefect/server/ui-v2/assets/{index-D5v9S-lB.js → index-k2161reF.js} +2 -2
  130. prefect/server/ui-v2/assets/{index-D5v9S-lB.js.map → index-k2161reF.js.map} +1 -1
  131. prefect/server/ui-v2/assets/{index-Bj0OOguP.js → index-pE_31vXF.js} +2 -2
  132. prefect/server/ui-v2/assets/{index-Bj0OOguP.js.map → index-pE_31vXF.js.map} +1 -1
  133. prefect/server/ui-v2/assets/{json-input-Dt1icmrn.js → json-input-C9dkBSdc.js} +2 -2
  134. prefect/server/ui-v2/assets/{json-input-Dt1icmrn.js.map → json-input-C9dkBSdc.js.map} +1 -1
  135. prefect/server/ui-v2/assets/{key._key-Cyh5MBX_.js → key._key-BBVH44_R.js} +2 -2
  136. prefect/server/ui-v2/assets/{key._key-Cyh5MBX_.js.map → key._key-BBVH44_R.js.map} +1 -1
  137. prefect/server/ui-v2/assets/{lazy-markdown-BwIwKFRF.js → lazy-markdown-BoMKGRXo.js} +2 -2
  138. prefect/server/ui-v2/assets/{lazy-markdown-BwIwKFRF.js.map → lazy-markdown-BoMKGRXo.js.map} +1 -1
  139. prefect/server/ui-v2/assets/{login-DKXFVSwk.js → login-BmmIDesJ.js} +2 -2
  140. prefect/server/ui-v2/assets/{login-DKXFVSwk.js.map → login-BmmIDesJ.js.map} +1 -1
  141. prefect/server/ui-v2/assets/{markdown-input-Dp0mBlkV.js → markdown-input-C6P8cYT1.js} +2 -2
  142. prefect/server/ui-v2/assets/{markdown-input-Dp0mBlkV.js.map → markdown-input-C6P8cYT1.js.map} +1 -1
  143. prefect/server/ui-v2/assets/{python-example-snippet-BYwPjHI5.js → python-example-snippet-DPumgTpL.js} +3 -3
  144. prefect/server/ui-v2/assets/{python-example-snippet-BYwPjHI5.js.map → python-example-snippet-DPumgTpL.js.map} +1 -1
  145. prefect/server/ui-v2/assets/{python-input-y26XMqXw.js → python-input-DX4uV8MF.js} +2 -2
  146. prefect/server/ui-v2/assets/{python-input-y26XMqXw.js.map → python-input-DX4uV8MF.js.map} +1 -1
  147. prefect/server/ui-v2/assets/{radio-group-D0van45v.js → radio-group-DeNK-Ob0.js} +2 -2
  148. prefect/server/ui-v2/assets/{radio-group-D0van45v.js.map → radio-group-DeNK-Ob0.js.map} +1 -1
  149. prefect/server/ui-v2/assets/{route-error-state-CGGpuCGN.js → route-error-state-C9CbByMx.js} +2 -2
  150. prefect/server/ui-v2/assets/{route-error-state-CGGpuCGN.js.map → route-error-state-C9CbByMx.js.map} +1 -1
  151. prefect/server/ui-v2/assets/{schema-form-2tg5SXM4.js → schema-form-B45xnsnK.js} +2 -2
  152. prefect/server/ui-v2/assets/{schema-form-2tg5SXM4.js.map → schema-form-B45xnsnK.js.map} +1 -1
  153. prefect/server/ui-v2/assets/{schema-form-input-string-format-datetime-CZt6AJ4z.js → schema-form-input-string-format-datetime-BJj81bu-.js} +4 -4
  154. prefect/server/ui-v2/assets/{schema-form-input-string-format-datetime-CZt6AJ4z.js.map → schema-form-input-string-format-datetime-BJj81bu-.js.map} +1 -1
  155. prefect/server/ui-v2/assets/{settings-DDUadk_N.js → settings-CbkeWqQ9.js} +2 -2
  156. prefect/server/ui-v2/assets/{settings-DDUadk_N.js.map → settings-CbkeWqQ9.js.map} +1 -1
  157. prefect/server/ui-v2/assets/{sort-filter-D9p3cPx9.js → sort-filter-DM7SvBB0.js} +2 -2
  158. prefect/server/ui-v2/assets/{sort-filter-D9p3cPx9.js.map → sort-filter-DM7SvBB0.js.map} +1 -1
  159. prefect/server/ui-v2/assets/{table-vo9Do8sA.js → table-CEPo6xb_.js} +2 -2
  160. prefect/server/ui-v2/assets/{table-vo9Do8sA.js.map → table-CEPo6xb_.js.map} +1 -1
  161. prefect/server/ui-v2/assets/{tags-input-Ci2JQ-k3.js → tags-input-DJiZSVHp.js} +2 -2
  162. prefect/server/ui-v2/assets/{tags-input-Ci2JQ-k3.js.map → tags-input-DJiZSVHp.js.map} +1 -1
  163. prefect/server/ui-v2/assets/{task-run-concurrency-limits-reset-dialog-CJzPc2gw.js → task-run-concurrency-limits-reset-dialog-vJEkvqWp.js} +2 -2
  164. prefect/server/ui-v2/assets/{task-run-concurrency-limits-reset-dialog-CJzPc2gw.js.map → task-run-concurrency-limits-reset-dialog-vJEkvqWp.js.map} +1 -1
  165. prefect/server/ui-v2/assets/{task-run._id-D8QKG5UZ.js → task-run._id-BCnDnXrL.js} +2 -2
  166. prefect/server/ui-v2/assets/{task-run._id-D8QKG5UZ.js.map → task-run._id-BCnDnXrL.js.map} +1 -1
  167. prefect/server/ui-v2/assets/{task-run._id-CjevSs79.js → task-run._id-ByJw1K82.js} +2 -2
  168. prefect/server/ui-v2/assets/{task-run._id-CjevSs79.js.map → task-run._id-ByJw1K82.js.map} +1 -1
  169. prefect/server/ui-v2/assets/{task-runs-pagination-CifoSGct.js → task-runs-pagination-DbjB6XnY.js} +2 -2
  170. prefect/server/ui-v2/assets/{task-runs-pagination-CifoSGct.js.map → task-runs-pagination-DbjB6XnY.js.map} +1 -1
  171. prefect/server/ui-v2/assets/{textarea-BAtfAxtV.js → textarea-BX8KbLGx.js} +2 -2
  172. prefect/server/ui-v2/assets/{textarea-BAtfAxtV.js.map → textarea-BX8KbLGx.js.map} +1 -1
  173. prefect/server/ui-v2/assets/{timezone-select-QlQTZSsF.js → timezone-select-TBj2n6Ox.js} +2 -2
  174. prefect/server/ui-v2/assets/{timezone-select-QlQTZSsF.js.map → timezone-select-TBj2n6Ox.js.map} +1 -1
  175. prefect/server/ui-v2/assets/{toggle-group-BJN1vjEh.js → toggle-group-D7mfSUim.js} +2 -2
  176. prefect/server/ui-v2/assets/{toggle-group-BJN1vjEh.js.map → toggle-group-D7mfSUim.js.map} +1 -1
  177. prefect/server/ui-v2/assets/{use-delete-automation-confirmation-dialog-eOWJYPkD.js → use-delete-automation-confirmation-dialog-BpBSjz60.js} +2 -2
  178. prefect/server/ui-v2/assets/{use-delete-automation-confirmation-dialog-eOWJYPkD.js.map → use-delete-automation-confirmation-dialog-BpBSjz60.js.map} +1 -1
  179. prefect/server/ui-v2/assets/{use-delete-block-document-confirmation-dialog-BTwSeHRM.js → use-delete-block-document-confirmation-dialog-BjKsNw6b.js} +2 -2
  180. prefect/server/ui-v2/assets/{use-delete-block-document-confirmation-dialog-BTwSeHRM.js.map → use-delete-block-document-confirmation-dialog-BjKsNw6b.js.map} +1 -1
  181. prefect/server/ui-v2/assets/{use-flow-runs-selected-rows-BL_Gv9CC.js → use-flow-runs-selected-rows-B_BlBMic.js} +2 -2
  182. prefect/server/ui-v2/assets/{use-flow-runs-selected-rows-BL_Gv9CC.js.map → use-flow-runs-selected-rows-B_BlBMic.js.map} +1 -1
  183. prefect/server/ui-v2/assets/{use-get-artifacts-flow-task-runs-TSCoomjQ.js → use-get-artifacts-flow-task-runs-BfE433bC.js} +2 -2
  184. prefect/server/ui-v2/assets/{use-get-artifacts-flow-task-runs-TSCoomjQ.js.map → use-get-artifacts-flow-task-runs-BfE433bC.js.map} +1 -1
  185. prefect/server/ui-v2/assets/{use-quick-run-BMnCkwSv.js → use-quick-run-qrtcF-DD.js} +2 -2
  186. prefect/server/ui-v2/assets/{use-quick-run-BMnCkwSv.js.map → use-quick-run-qrtcF-DD.js.map} +1 -1
  187. prefect/server/ui-v2/assets/{use-stepper-C1wm66U2.js → use-stepper-CVy1_GBw.js} +2 -2
  188. prefect/server/ui-v2/assets/{use-stepper-C1wm66U2.js.map → use-stepper-CVy1_GBw.js.map} +1 -1
  189. prefect/server/ui-v2/assets/{utilities-DxRXxFOF.js → utilities-D5-00wjP.js} +2 -2
  190. prefect/server/ui-v2/assets/{utilities-DxRXxFOF.js.map → utilities-D5-00wjP.js.map} +1 -1
  191. prefect/server/ui-v2/assets/{vendor-graphs-Diy0Lo-N.js → vendor-graphs-R8z6S3Jg.js} +2 -2
  192. prefect/server/ui-v2/assets/{vendor-graphs-Diy0Lo-N.js.map → vendor-graphs-R8z6S3Jg.js.map} +1 -1
  193. prefect/server/ui-v2/assets/{work-pool-filter-PudrkZYj.js → work-pool-filter-D8MkTbSW.js} +2 -2
  194. prefect/server/ui-v2/assets/{work-pool-filter-PudrkZYj.js.map → work-pool-filter-D8MkTbSW.js.map} +1 -1
  195. prefect/server/ui-v2/assets/{work-pool-queue-toggle-BAOrV_0R.js → work-pool-queue-toggle-CbI7Gfu5.js} +2 -2
  196. prefect/server/ui-v2/assets/{work-pool-queue-toggle-BAOrV_0R.js.map → work-pool-queue-toggle-CbI7Gfu5.js.map} +1 -1
  197. prefect/server/ui-v2/assets/{work-pool._workPoolName-BOM3849e.js → work-pool._workPoolName-CCzzgVV2.js} +2 -2
  198. prefect/server/ui-v2/assets/{work-pool._workPoolName-BOM3849e.js.map → work-pool._workPoolName-CCzzgVV2.js.map} +1 -1
  199. prefect/server/ui-v2/assets/{work-pool_._workPoolName.edit-CIhcG6yr.js → work-pool_._workPoolName.edit-BAgiuU_j.js} +2 -2
  200. prefect/server/ui-v2/assets/{work-pool_._workPoolName.edit-CIhcG6yr.js.map → work-pool_._workPoolName.edit-BAgiuU_j.js.map} +1 -1
  201. prefect/server/ui-v2/assets/{work-pool_._workPoolName.queue._workQueueName-CjoM77tu.js → work-pool_._workPoolName.queue._workQueueName-BUawWJEy.js} +2 -2
  202. prefect/server/ui-v2/assets/{work-pool_._workPoolName.queue._workQueueName-CjoM77tu.js.map → work-pool_._workPoolName.queue._workQueueName-BUawWJEy.js.map} +1 -1
  203. prefect/server/ui-v2/assets/{work-queue-icon-text-CP4yX3uM.js → work-queue-icon-text-D_G_UCJX.js} +2 -2
  204. prefect/server/ui-v2/assets/{work-queue-icon-text-CP4yX3uM.js.map → work-queue-icon-text-D_G_UCJX.js.map} +1 -1
  205. prefect/server/ui-v2/index.html +2 -2
  206. prefect/settings/models/flows.py +14 -1
  207. prefect/settings/models/runner.py +16 -6
  208. prefect/task_engine.py +1 -3
  209. prefect/task_worker.py +1 -1
  210. prefect/tasks.py +2 -2
  211. prefect/testing/fixtures.py +23 -5
  212. {prefect-3.6.13.dev2.dist-info → prefect-3.6.14.dev1.dist-info}/METADATA +1 -1
  213. {prefect-3.6.13.dev2.dist-info → prefect-3.6.14.dev1.dist-info}/RECORD +216 -216
  214. prefect/server/ui-v2/assets/block-type-logo-WQNm0PzP.js +0 -2
  215. prefect/server/ui-v2/assets/deployment_._id.duplicate-DB-4hHHc.js +0 -2
  216. prefect/server/ui-v2/assets/deployment_._id.edit-CWf1RIGy.js +0 -2
  217. prefect/server/ui-v2/assets/flow-run-graph-DxIl6fzW.js +0 -2
  218. prefect/server/ui-v2/assets/flow-run-graph-DxIl6fzW.js.map +0 -1
  219. prefect/server/ui-v2/assets/flow-run._id-DJuMECRh.js +0 -4
  220. prefect/server/ui-v2/assets/flow-run._id-DJuMECRh.js.map +0 -1
  221. prefect/server/ui-v2/assets/index-BvSl3DKP.js +0 -17
  222. prefect/server/ui-v2/assets/index-BvSl3DKP.js.map +0 -1
  223. prefect/server/ui-v2/assets/index-NY089eTx.css +0 -1
  224. {prefect-3.6.13.dev2.dist-info → prefect-3.6.14.dev1.dist-info}/WHEEL +0 -0
  225. {prefect-3.6.13.dev2.dist-info → prefect-3.6.14.dev1.dist-info}/entry_points.txt +0 -0
  226. {prefect-3.6.13.dev2.dist-info → prefect-3.6.14.dev1.dist-info}/licenses/LICENSE +0 -0
prefect/results.py CHANGED
@@ -33,7 +33,10 @@ 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 call_explicitly_async_block_method
36
+ from prefect._internal.compatibility.blocks import (
37
+ call_explicitly_async_block_method,
38
+ call_explicitly_sync_block_method,
39
+ )
37
40
  from prefect._internal.concurrency.event_loop import get_running_loop
38
41
  from prefect._result_records import R, ResultRecord, ResultRecordMetadata
39
42
  from prefect.blocks.core import Block
@@ -52,7 +55,6 @@ from prefect.serializers import Serializer
52
55
  from prefect.settings.context import get_current_settings
53
56
  from prefect.types import DateTime
54
57
  from prefect.utilities.annotations import NotSet
55
- from prefect.utilities.asyncutils import sync_compatible
56
58
 
57
59
  if TYPE_CHECKING:
58
60
  import logging
@@ -369,8 +371,7 @@ class ResultStore(BaseModel):
369
371
  result = await store.aread(metadata.storage_key)
370
372
  return result
371
373
 
372
- @sync_compatible
373
- async def update_for_flow(self, flow: "Flow[..., Any]") -> Self:
374
+ async def aupdate_for_flow(self, flow: "Flow[..., Any]") -> Self:
374
375
  """
375
376
  Create a new result store for a flow with updated settings.
376
377
 
@@ -393,8 +394,31 @@ class ResultStore(BaseModel):
393
394
  update["metadata_storage"] = NullFileSystem()
394
395
  return self.model_copy(update=update)
395
396
 
396
- @sync_compatible
397
- async def update_for_task(self: Self, task: "Task[P, R]") -> Self:
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:
398
422
  """
399
423
  Create a new result store for a task.
400
424
 
@@ -446,6 +470,59 @@ class ResultStore(BaseModel):
446
470
  update["metadata_storage"] = None
447
471
  return self.model_copy(update=update)
448
472
 
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
+
449
526
  @staticmethod
450
527
  def generate_default_holder() -> str:
451
528
  """
@@ -467,8 +544,7 @@ class ResultStore(BaseModel):
467
544
  return f"{hostname}:{pid}:{thread_id}:{thread_name}:{id(current_task)}"
468
545
  return f"{hostname}:{pid}:{thread_id}:{thread_name}"
469
546
 
470
- @sync_compatible
471
- async def _exists(self, key: str) -> bool:
547
+ async def _aexists(self, key: str) -> bool:
472
548
  """
473
549
  Check if a result record exists in storage.
474
550
 
@@ -511,6 +587,49 @@ class ResultStore(BaseModel):
511
587
  exists = True
512
588
  return exists
513
589
 
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
+
514
633
  def exists(self, key: str) -> bool:
515
634
  """
516
635
  Check if a result record exists in storage.
@@ -521,7 +640,7 @@ class ResultStore(BaseModel):
521
640
  Returns:
522
641
  bool: True if the result record exists, False otherwise.
523
642
  """
524
- return self._exists(key=key, _sync=True)
643
+ return self._exists(key=key)
525
644
 
526
645
  async def aexists(self, key: str) -> bool:
527
646
  """
@@ -533,7 +652,7 @@ class ResultStore(BaseModel):
533
652
  Returns:
534
653
  bool: True if the result record exists, False otherwise.
535
654
  """
536
- return await self._exists(key=key, _sync=False)
655
+ return await self._aexists(key=key)
537
656
 
538
657
  def _resolved_key_path(self, key: str) -> str:
539
658
  if self.result_storage_block_id is None and (
@@ -546,8 +665,7 @@ class ResultStore(BaseModel):
546
665
  return key
547
666
  return key
548
667
 
549
- @sync_compatible
550
- async def _read(self, key: str, holder: str) -> "ResultRecord[Any]":
668
+ async def _aread(self, key: str, holder: str) -> "ResultRecord[Any]":
551
669
  """
552
670
  Read a result record from storage.
553
671
 
@@ -610,6 +728,69 @@ class ResultStore(BaseModel):
610
728
  self.cache[resolved_key_path] = result_record
611
729
  return result_record
612
730
 
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
+
613
794
  def read(
614
795
  self,
615
796
  key: str,
@@ -626,7 +807,7 @@ class ResultStore(BaseModel):
626
807
  A result record.
627
808
  """
628
809
  holder = holder or self.generate_default_holder()
629
- return self._read(key=key, holder=holder, _sync=True)
810
+ return self._read(key=key, holder=holder)
630
811
 
631
812
  async def aread(
632
813
  self,
@@ -644,7 +825,7 @@ class ResultStore(BaseModel):
644
825
  A result record.
645
826
  """
646
827
  holder = holder or self.generate_default_holder()
647
- return await self._read(key=key, holder=holder, _sync=False)
828
+ return await self._aread(key=key, holder=holder)
648
829
 
649
830
  def create_result_record(
650
831
  self,
@@ -734,8 +915,7 @@ class ResultStore(BaseModel):
734
915
  holder=holder,
735
916
  )
736
917
 
737
- @sync_compatible
738
- async def _persist_result_record(
918
+ async def _apersist_result_record(
739
919
  self, result_record: "ResultRecord[Any]", holder: str
740
920
  ) -> None:
741
921
  """
@@ -798,6 +978,69 @@ class ResultStore(BaseModel):
798
978
  if self.cache_result_in_memory:
799
979
  self.cache[key] = result_record
800
980
 
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
+
801
1044
  def persist_result_record(
802
1045
  self, result_record: "ResultRecord[Any]", holder: str | None = None
803
1046
  ) -> None:
@@ -808,9 +1051,7 @@ class ResultStore(BaseModel):
808
1051
  result_record: The result record to persist.
809
1052
  """
810
1053
  holder = holder or self.generate_default_holder()
811
- return self._persist_result_record(
812
- result_record=result_record, holder=holder, _sync=True
813
- )
1054
+ return self._persist_result_record(result_record=result_record, holder=holder)
814
1055
 
815
1056
  async def apersist_result_record(
816
1057
  self, result_record: "ResultRecord[Any]", holder: str | None = None
@@ -822,8 +1063,8 @@ class ResultStore(BaseModel):
822
1063
  result_record: The result record to persist.
823
1064
  """
824
1065
  holder = holder or self.generate_default_holder()
825
- return await self._persist_result_record(
826
- result_record=result_record, holder=holder, _sync=False
1066
+ return await self._apersist_result_record(
1067
+ result_record=result_record, holder=holder
827
1068
  )
828
1069
 
829
1070
  def supports_isolation_level(self, level: "IsolationLevel") -> bool:
prefect/runner/runner.py CHANGED
@@ -56,7 +56,6 @@ from typing import (
56
56
  TYPE_CHECKING,
57
57
  Any,
58
58
  Callable,
59
- Coroutine,
60
59
  Dict,
61
60
  Iterable,
62
61
  List,
@@ -167,9 +166,10 @@ class Runner:
167
166
  query_seconds: The number of seconds to wait between querying for
168
167
  scheduled flow runs; defaults to `PREFECT_RUNNER_POLL_FREQUENCY`
169
168
  prefetch_seconds: The number of seconds to prefetch flow runs for.
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`.
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.
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,6 +202,8 @@ class Runner:
202
202
  asyncio.run(runner.start())
203
203
  ```
204
204
  """
205
+ self._heartbeat_seconds = heartbeat_seconds
206
+
205
207
  settings = get_current_settings()
206
208
 
207
209
  if name and ("/" in name or "%" in name):
@@ -221,12 +223,6 @@ class Runner:
221
223
 
222
224
  self.query_seconds: float = query_seconds or settings.runner.poll_frequency
223
225
  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
230
226
  self._events_client: EventsClient = get_events_client(checkpoint_every=1)
231
227
 
232
228
  self._exit_stack = AsyncExitStack()
@@ -644,9 +640,6 @@ class Runner:
644
640
 
645
641
  task_status.started(process.pid)
646
642
 
647
- if self.heartbeat_seconds is not None:
648
- await self._emit_flow_run_heartbeat(flow_run)
649
-
650
643
  # Only add the process to the map if it is still running
651
644
  # The process may be a multiprocessing.context.SpawnProcess, in which case it will have an `exitcode`` attribute
652
645
  # but no `returncode` attribute
@@ -682,6 +675,11 @@ class Runner:
682
675
 
683
676
  flow_run = FlowRun.model_validate(bundle["flow_run"])
684
677
 
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
+
685
683
  async with context:
686
684
  if not self._acquire_limit_slot(flow_run.id):
687
685
  return
@@ -695,9 +693,6 @@ class Runner:
695
693
  await self._propose_crashed_state(flow_run, msg)
696
694
  raise RuntimeError(msg)
697
695
 
698
- if self.heartbeat_seconds is not None:
699
- await self._emit_flow_run_heartbeat(flow_run)
700
-
701
696
  await self._add_flow_run_process_map_entry(
702
697
  flow_run.id, ProcessMapEntry(pid=process.pid, flow_run=flow_run)
703
698
  )
@@ -796,7 +791,14 @@ class Runner:
796
791
  if flow_run.deployment_id is not None:
797
792
  flow = self._deployment_flow_map.get(flow_run.deployment_id)
798
793
  if flow:
799
- process = run_flow_in_subprocess(flow, flow_run=flow_run)
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
+ )
800
802
  task_status.started(process)
801
803
  await anyio.to_thread.run_sync(process.join)
802
804
  return process.exitcode
@@ -829,6 +831,15 @@ class Runner:
829
831
  "PREFECT__ENABLE_CANCELLATION_AND_CRASHED_HOOKS": "false",
830
832
  },
831
833
  **({"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
+ ),
832
843
  }
833
844
  )
834
845
  env.update(**os.environ) # is this really necessary??
@@ -1049,51 +1060,6 @@ class Runner:
1049
1060
  flow = None
1050
1061
  return flow, deployment
1051
1062
 
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
-
1097
1063
  def _event_resource(self):
1098
1064
  from prefect import __version__
1099
1065
 
@@ -1145,7 +1111,7 @@ class Runner:
1145
1111
  related=related,
1146
1112
  )
1147
1113
  )
1148
- self._logger.debug(f"Emitted flow run heartbeat event for {flow_run.id}")
1114
+ self._logger.debug(f"Emitted cancelled-flow-run event for {flow_run.id}")
1149
1115
 
1150
1116
  async def _get_scheduled_flow_runs(
1151
1117
  self,
@@ -1289,9 +1255,6 @@ class Runner:
1289
1255
  flow_run.id,
1290
1256
  ProcessMapEntry(pid=readiness_result.pid, flow_run=flow_run),
1291
1257
  )
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)
1295
1258
 
1296
1259
  run_logger.info(f"Completed submission of flow run '{flow_run.id}'")
1297
1260
  else:
@@ -1605,15 +1568,6 @@ class Runner:
1605
1568
  if not hasattr(self, "_loops_task_group") or not self._loops_task_group:
1606
1569
  self._loops_task_group: anyio.abc.TaskGroup = anyio.create_task_group()
1607
1570
 
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
-
1617
1571
  self.started = True
1618
1572
  return self
1619
1573
 
@@ -1632,13 +1586,6 @@ class Runner:
1632
1586
  shutil.rmtree(str(self._tmp_dir), ignore_errors=True)
1633
1587
  del self._runs_task_group, self._loops_task_group
1634
1588
 
1635
- if self._heartbeat_task:
1636
- self._heartbeat_task.cancel()
1637
- try:
1638
- await self._heartbeat_task
1639
- except asyncio.CancelledError:
1640
- pass
1641
-
1642
1589
  def __repr__(self) -> str:
1643
1590
  return f"Runner(name={self.name!r})"
1644
1591
 
prefect/runner/storage.py CHANGED
@@ -346,11 +346,12 @@ 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)
349
350
 
350
- if existing_repo_url != self._url:
351
+ if existing_repo_url != configured_repo_url:
351
352
  raise ValueError(
352
353
  f"The existing repository at {str(self.destination)} "
353
- f"does not match the configured repository {self._url}"
354
+ f"does not match the configured repository {configured_repo_url}"
354
355
  )
355
356
 
356
357
  # Sparsely checkout the repository if directories are specified and the repo is not in sparse-checkout mode already
@@ -645,6 +645,45 @@ 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
+
648
687
  class FlowRunFilter(PrefectOperatorFilterBaseModel):
649
688
  """Filter flow runs. Only flow runs matching all criteria will be returned"""
650
689
 
@@ -691,6 +730,9 @@ class FlowRunFilter(PrefectOperatorFilterBaseModel):
691
730
  idempotency_key: Optional[FlowRunFilterIdempotencyKey] = Field(
692
731
  default=None, description="Filter criteria for `FlowRun.idempotency_key`"
693
732
  )
733
+ created_by: Optional[FlowRunFilterCreatedBy] = Field(
734
+ default=None, description="Filter criteria for `FlowRun.created_by`"
735
+ )
694
736
 
695
737
  def only_filters_on_id(self) -> bool:
696
738
  return bool(
@@ -709,6 +751,7 @@ class FlowRunFilter(PrefectOperatorFilterBaseModel):
709
751
  and self.parent_flow_run_id is None
710
752
  and self.parent_task_run_id is None
711
753
  and self.idempotency_key is None
754
+ and self.created_by is None
712
755
  )
713
756
 
714
757
  def _get_filter_list(
@@ -744,6 +787,8 @@ class FlowRunFilter(PrefectOperatorFilterBaseModel):
744
787
  filters.append(self.parent_task_run_id.as_sql_filter())
745
788
  if self.idempotency_key is not None:
746
789
  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())
747
792
 
748
793
  return filters
749
794