prefect 3.6.12__py3-none-any.whl → 3.6.13__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.
- prefect/_build_info.py +3 -3
- prefect/_internal/compatibility/blocks.py +18 -0
- prefect/_internal/urls.py +29 -0
- prefect/_states.py +4 -1
- prefect/client/orchestration/_concurrency_limits/client.py +16 -16
- prefect/client/schemas/filters.py +24 -0
- prefect/client/subscriptions.py +1 -1
- prefect/events/clients.py +52 -13
- prefect/flow_engine.py +192 -10
- prefect/flows.py +61 -2
- prefect/results.py +262 -21
- prefect/runner/runner.py +29 -82
- prefect/runner/storage.py +6 -25
- prefect/server/schemas/filters.py +45 -0
- prefect/server/ui-v2/assets/{artifact-card-Ds2tntMW.js → artifact-card-CUEaRDGw.js} +2 -2
- prefect/server/ui-v2/assets/{artifact-card-Ds2tntMW.js.map → artifact-card-CUEaRDGw.js.map} +1 -1
- prefect/server/ui-v2/assets/{artifact._id-erPNL0a8.js → artifact._id-Ca6VCUS0.js} +2 -2
- prefect/server/ui-v2/assets/{artifact._id-erPNL0a8.js.map → artifact._id-Ca6VCUS0.js.map} +1 -1
- prefect/server/ui-v2/assets/{automation-wizard-Bx9AQxRV.js → automation-wizard-z26pICBl.js} +2 -2
- prefect/server/ui-v2/assets/{automation-wizard-Bx9AQxRV.js.map → automation-wizard-z26pICBl.js.map} +1 -1
- prefect/server/ui-v2/assets/automation._id-DuodjY5t.js +2 -0
- prefect/server/ui-v2/assets/{automation._id-IHh5QJwE.js.map → automation._id-DuodjY5t.js.map} +1 -1
- prefect/server/ui-v2/assets/{automation_._id.edit-Ds8vO0c5.js → automation_._id.edit-CcuJhc-y.js} +2 -2
- prefect/server/ui-v2/assets/{automation_._id.edit-Ds8vO0c5.js.map → automation_._id.edit-CcuJhc-y.js.map} +1 -1
- prefect/server/ui-v2/assets/{automations-header-C6aUe5Tw.js → automations-header-CgOWwuc6.js} +2 -2
- prefect/server/ui-v2/assets/{automations-header-C6aUe5Tw.js.map → automations-header-CgOWwuc6.js.map} +1 -1
- prefect/server/ui-v2/assets/{base-job-template-form-section-a-9TFMcW.js → base-job-template-form-section-DN9HyjM5.js} +2 -2
- prefect/server/ui-v2/assets/{base-job-template-form-section-a-9TFMcW.js.map → base-job-template-form-section-DN9HyjM5.js.map} +1 -1
- prefect/server/ui-v2/assets/{block-type-details-eq3Wg2ht.js → block-type-details-rrToxL5r.js} +2 -2
- prefect/server/ui-v2/assets/{block-type-details-eq3Wg2ht.js.map → block-type-details-rrToxL5r.js.map} +1 -1
- prefect/server/ui-v2/assets/block-type-logo-DURScH8H.js +2 -0
- prefect/server/ui-v2/assets/{block-type-logo-B31LJm5H.js.map → block-type-logo-DURScH8H.js.map} +1 -1
- prefect/server/ui-v2/assets/{block._id-OVdz3htC.js → block._id-Bue6lnrN.js} +2 -2
- prefect/server/ui-v2/assets/{block._id-OVdz3htC.js.map → block._id-Bue6lnrN.js.map} +1 -1
- prefect/server/ui-v2/assets/block_._id.edit-QY0OzN_b.js +2 -0
- prefect/server/ui-v2/assets/{block_._id.edit-CIhIBJrm.js.map → block_._id.edit-QY0OzN_b.js.map} +1 -1
- prefect/server/ui-v2/assets/catalog-CGjRGWv6.js +2 -0
- prefect/server/ui-v2/assets/{catalog-CfniU0UV.js.map → catalog-CGjRGWv6.js.map} +1 -1
- prefect/server/ui-v2/assets/catalog_._slug-BMFNZhEb.js +2 -0
- prefect/server/ui-v2/assets/{catalog_._slug-C9p86T4s.js.map → catalog_._slug-BMFNZhEb.js.map} +1 -1
- prefect/server/ui-v2/assets/catalog_._slug_.create-CuFA3kUC.js +2 -0
- prefect/server/ui-v2/assets/{catalog_._slug_.create-BhSunL__.js.map → catalog_._slug_.create-CuFA3kUC.js.map} +1 -1
- prefect/server/ui-v2/assets/{collapsible-DBD7wjpi.js → collapsible-Ell2FjrX.js} +2 -2
- prefect/server/ui-v2/assets/{collapsible-DBD7wjpi.js.map → collapsible-Ell2FjrX.js.map} +1 -1
- prefect/server/ui-v2/assets/{concurrency-limit._id-C-XWq7Tf.js → concurrency-limit._id-Rr1ysUct.js} +2 -2
- prefect/server/ui-v2/assets/{concurrency-limit._id-C-XWq7Tf.js.map → concurrency-limit._id-Rr1ysUct.js.map} +1 -1
- prefect/server/ui-v2/assets/{create-noFojqGL.js → create-CQK8-uO1.js} +2 -2
- prefect/server/ui-v2/assets/{create-noFojqGL.js.map → create-CQK8-uO1.js.map} +1 -1
- prefect/server/ui-v2/assets/{create-Bj2t5YQq.js → create-ewA3uq_h.js} +2 -2
- prefect/server/ui-v2/assets/{create-Bj2t5YQq.js.map → create-ewA3uq_h.js.map} +1 -1
- prefect/server/ui-v2/assets/{data-table-BjFlVIyC.js → data-table-Bx1uYX_M.js} +2 -2
- prefect/server/ui-v2/assets/{data-table-BjFlVIyC.js.map → data-table-Bx1uYX_M.js.map} +1 -1
- prefect/server/ui-v2/assets/delete-confirmation-dialog-CqKsUEj_.js +2 -0
- prefect/server/ui-v2/assets/{delete-confirmation-dialog-COdZmNfa.js.map → delete-confirmation-dialog-CqKsUEj_.js.map} +1 -1
- prefect/server/ui-v2/assets/{deployment-action-header-C6v2kZ6V.js → deployment-action-header-Bz5COdER.js} +2 -2
- prefect/server/ui-v2/assets/{deployment-action-header-C6v2kZ6V.js.map → deployment-action-header-Bz5COdER.js.map} +1 -1
- prefect/server/ui-v2/assets/{deployment-form-D9d5sZM2.js → deployment-form-CrlZlNoj.js} +3 -3
- prefect/server/ui-v2/assets/{deployment-form-D9d5sZM2.js.map → deployment-form-CrlZlNoj.js.map} +1 -1
- prefect/server/ui-v2/assets/{deployment-links-Dy-M1Que.js → deployment-links-D9ZR_vmp.js} +2 -2
- prefect/server/ui-v2/assets/{deployment-links-Dy-M1Que.js.map → deployment-links-D9ZR_vmp.js.map} +1 -1
- prefect/server/ui-v2/assets/deployment._id-BV0rSqba.js +2 -0
- prefect/server/ui-v2/assets/{deployment._id-DVmqclRz.js.map → deployment._id-BV0rSqba.js.map} +1 -1
- prefect/server/ui-v2/assets/{deployment._id-CZlY9261.js → deployment._id-DvHhx-qN.js} +2 -2
- prefect/server/ui-v2/assets/{deployment._id-CZlY9261.js.map → deployment._id-DvHhx-qN.js.map} +1 -1
- prefect/server/ui-v2/assets/deployment_._id.duplicate-BrEOenqP.js +2 -0
- prefect/server/ui-v2/assets/{deployment_._id.duplicate-DVMjpk5m.js.map → deployment_._id.duplicate-BrEOenqP.js.map} +1 -1
- prefect/server/ui-v2/assets/deployment_._id.edit-BbYKPK42.js +2 -0
- prefect/server/ui-v2/assets/{deployment_._id.edit-Ck_P6KDn.js.map → deployment_._id.edit-BbYKPK42.js.map} +1 -1
- prefect/server/ui-v2/assets/{deployment_._id.run-Cht7AHDG.js → deployment_._id.run-Dv7S_MJR.js} +2 -2
- prefect/server/ui-v2/assets/{deployment_._id.run-Cht7AHDG.js.map → deployment_._id.run-Dv7S_MJR.js.map} +1 -1
- prefect/server/ui-v2/assets/{dropdown-menu-xZ_3w9OP.js → dropdown-menu-e0Fqb6aw.js} +2 -2
- prefect/server/ui-v2/assets/{dropdown-menu-xZ_3w9OP.js.map → dropdown-menu-e0Fqb6aw.js.map} +1 -1
- prefect/server/ui-v2/assets/{event._eventDate._eventId-CpexuXd6.js → event._eventDate._eventId-Cp4UmGqq.js} +2 -2
- prefect/server/ui-v2/assets/{event._eventDate._eventId-CpexuXd6.js.map → event._eventDate._eventId-Cp4UmGqq.js.map} +1 -1
- prefect/server/ui-v2/assets/flow-run-graph-BrqoR3E2.js +2 -0
- prefect/server/ui-v2/assets/flow-run-graph-BrqoR3E2.js.map +1 -0
- prefect/server/ui-v2/assets/{flow-run._id-ZBlKBwPO.js → flow-run._id-BOp38Pbq.js} +2 -2
- prefect/server/ui-v2/assets/{flow-run._id-ZBlKBwPO.js.map → flow-run._id-BOp38Pbq.js.map} +1 -1
- prefect/server/ui-v2/assets/flow-run._id-DJnTDEN_.js +2 -0
- prefect/server/ui-v2/assets/{flow-run._id-OL0YhyLW.js.map → flow-run._id-DJnTDEN_.js.map} +1 -1
- prefect/server/ui-v2/assets/flow-run._id-D_wY_rBe.js +4 -0
- prefect/server/ui-v2/assets/flow-run._id-D_wY_rBe.js.map +1 -0
- prefect/server/ui-v2/assets/flow-runs-pagination-Bq2ZUzM6.js +2 -0
- prefect/server/ui-v2/assets/flow-runs-pagination-Bq2ZUzM6.js.map +1 -0
- prefect/server/ui-v2/assets/flow._id-eCBL95rg.js +2 -0
- prefect/server/ui-v2/assets/{flow._id-DhrCicwR.js.map → flow._id-eCBL95rg.js.map} +1 -1
- prefect/server/ui-v2/assets/{form-BTub_PhK.js → form-DNerk3LS.js} +2 -2
- prefect/server/ui-v2/assets/{form-BTub_PhK.js.map → form-DNerk3LS.js.map} +1 -1
- prefect/server/ui-v2/assets/{header-6wmrKLU3.js → header-B0ejRncu.js} +2 -2
- prefect/server/ui-v2/assets/{header-6wmrKLU3.js.map → header-B0ejRncu.js.map} +1 -1
- prefect/server/ui-v2/assets/{header-Dp9qi8fq.js → header-DwagHBlF.js} +2 -2
- prefect/server/ui-v2/assets/{header-Dp9qi8fq.js.map → header-DwagHBlF.js.map} +1 -1
- prefect/server/ui-v2/assets/{header-B75eb688.js → header-huSvwxKI.js} +2 -2
- prefect/server/ui-v2/assets/{header-B75eb688.js.map → header-huSvwxKI.js.map} +1 -1
- prefect/server/ui-v2/assets/{index-DSaSov8V.js → index-BO3SOwdV.js} +2 -2
- prefect/server/ui-v2/assets/{index-DSaSov8V.js.map → index-BO3SOwdV.js.map} +1 -1
- prefect/server/ui-v2/assets/{index-DOkFJdYY.js → index-BTPE3vs7.js} +2 -2
- prefect/server/ui-v2/assets/{index-DOkFJdYY.js.map → index-BTPE3vs7.js.map} +1 -1
- prefect/server/ui-v2/assets/index-BU4yZRd3.js +2 -0
- prefect/server/ui-v2/assets/{index-Cs8eFQKw.js.map → index-BU4yZRd3.js.map} +1 -1
- prefect/server/ui-v2/assets/index-BhALpenO.js +2 -0
- prefect/server/ui-v2/assets/{index-qPlIYf3i.js.map → index-BhALpenO.js.map} +1 -1
- prefect/server/ui-v2/assets/index-BzN9bQeM.js +2 -0
- prefect/server/ui-v2/assets/{index-CT_nG86y.js.map → index-BzN9bQeM.js.map} +1 -1
- prefect/server/ui-v2/assets/{index-zpb5iSCL.js → index-CBhi1P9g.js} +2 -2
- prefect/server/ui-v2/assets/{index-zpb5iSCL.js.map → index-CBhi1P9g.js.map} +1 -1
- prefect/server/ui-v2/assets/{index-UN2Tx4jH.js → index-CRDz4nhM.js} +2 -2
- prefect/server/ui-v2/assets/{index-UN2Tx4jH.js.map → index-CRDz4nhM.js.map} +1 -1
- prefect/server/ui-v2/assets/index-CTnoa3Ho.js +2 -0
- prefect/server/ui-v2/assets/index-CTnoa3Ho.js.map +1 -0
- prefect/server/ui-v2/assets/{index-DzMGV8GV.js → index-CWkbSdxY.js} +2 -2
- prefect/server/ui-v2/assets/{index-DzMGV8GV.js.map → index-CWkbSdxY.js.map} +1 -1
- prefect/server/ui-v2/assets/index-CgOsOj5t.js +2 -0
- prefect/server/ui-v2/assets/{index-7ThYp9SY.js.map → index-CgOsOj5t.js.map} +1 -1
- prefect/server/ui-v2/assets/{index-CGWoVV2s.js → index-CnIJUujl.js} +2 -2
- prefect/server/ui-v2/assets/{index-CGWoVV2s.js.map → index-CnIJUujl.js.map} +1 -1
- prefect/server/ui-v2/assets/{index-D08xgmV0.js → index-Cutg_A1j.js} +2 -2
- prefect/server/ui-v2/assets/{index-D08xgmV0.js.map → index-Cutg_A1j.js.map} +1 -1
- prefect/server/ui-v2/assets/index-D5RdrxkU.js +17 -0
- prefect/server/ui-v2/assets/index-D5RdrxkU.js.map +1 -0
- prefect/server/ui-v2/assets/{index-DfiNsXba.js → index-DDiyFpIV.js} +2 -2
- prefect/server/ui-v2/assets/{index-DfiNsXba.js.map → index-DDiyFpIV.js.map} +1 -1
- prefect/server/ui-v2/assets/{index-BA7ACCoL.js → index-DI2DC5gd.js} +2 -2
- prefect/server/ui-v2/assets/{index-BA7ACCoL.js.map → index-DI2DC5gd.js.map} +1 -1
- prefect/server/ui-v2/assets/index-Dg_duvDx.js +2 -0
- prefect/server/ui-v2/assets/{index-B_3f8Hcb.js.map → index-Dg_duvDx.js.map} +1 -1
- prefect/server/ui-v2/assets/index-Dspw5HFj.js +2 -0
- prefect/server/ui-v2/assets/{index-7-r4ia_S.js.map → index-Dspw5HFj.js.map} +1 -1
- prefect/server/ui-v2/assets/index-NY089eTx.css +1 -0
- prefect/server/ui-v2/assets/{index-DYOACRXY.js → index-VOOLxiSE.js} +2 -2
- prefect/server/ui-v2/assets/{index-DYOACRXY.js.map → index-VOOLxiSE.js.map} +1 -1
- prefect/server/ui-v2/assets/{index-HGoNWFfP.js → index-Wfs7Cjew.js} +2 -2
- prefect/server/ui-v2/assets/{index-HGoNWFfP.js.map → index-Wfs7Cjew.js.map} +1 -1
- prefect/server/ui-v2/assets/index-dSUEBAqg.js +2 -0
- prefect/server/ui-v2/assets/{index-H6bwm6L6.js.map → index-dSUEBAqg.js.map} +1 -1
- prefect/server/ui-v2/assets/{index-D3ILnEzm.js → index-uvH5a3zO.js} +2 -2
- prefect/server/ui-v2/assets/{index-D3ILnEzm.js.map → index-uvH5a3zO.js.map} +1 -1
- prefect/server/ui-v2/assets/{json-input-9UPGqxTw.js → json-input-gXz7BuJj.js} +2 -2
- prefect/server/ui-v2/assets/{json-input-9UPGqxTw.js.map → json-input-gXz7BuJj.js.map} +1 -1
- prefect/server/ui-v2/assets/{key._key-CTFfXO_k.js → key._key-CJPbLXwU.js} +2 -2
- prefect/server/ui-v2/assets/{key._key-CTFfXO_k.js.map → key._key-CJPbLXwU.js.map} +1 -1
- prefect/server/ui-v2/assets/{lazy-markdown-BHwIrC8E.js → lazy-markdown-wPid80zf.js} +2 -2
- prefect/server/ui-v2/assets/{lazy-markdown-BHwIrC8E.js.map → lazy-markdown-wPid80zf.js.map} +1 -1
- prefect/server/ui-v2/assets/{login-kqmT29n7.js → login-DC63bGXK.js} +2 -2
- prefect/server/ui-v2/assets/{login-kqmT29n7.js.map → login-DC63bGXK.js.map} +1 -1
- prefect/server/ui-v2/assets/{markdown-input-BesmAbLS.js → markdown-input-BhqrU6Eo.js} +2 -2
- prefect/server/ui-v2/assets/{markdown-input-BesmAbLS.js.map → markdown-input-BhqrU6Eo.js.map} +1 -1
- prefect/server/ui-v2/assets/{python-example-snippet-COTWYn1Y.js → python-example-snippet-3jtXWQZN.js} +3 -3
- prefect/server/ui-v2/assets/{python-example-snippet-COTWYn1Y.js.map → python-example-snippet-3jtXWQZN.js.map} +1 -1
- prefect/server/ui-v2/assets/{python-input-Bjccebi0.js → python-input-ZVi-v324.js} +2 -2
- prefect/server/ui-v2/assets/{python-input-Bjccebi0.js.map → python-input-ZVi-v324.js.map} +1 -1
- prefect/server/ui-v2/assets/{radio-group-DkAK0M2h.js → radio-group-BBMLpHGc.js} +2 -2
- prefect/server/ui-v2/assets/{radio-group-DkAK0M2h.js.map → radio-group-BBMLpHGc.js.map} +1 -1
- prefect/server/ui-v2/assets/route-error-state-BJXl8qkX.js +2 -0
- prefect/server/ui-v2/assets/{route-error-state-ALftyvGl.js.map → route-error-state-BJXl8qkX.js.map} +1 -1
- prefect/server/ui-v2/assets/{schema-form-BR4E-WXE.js → schema-form-BEqYjsM-.js} +2 -2
- prefect/server/ui-v2/assets/{schema-form-BR4E-WXE.js.map → schema-form-BEqYjsM-.js.map} +1 -1
- prefect/server/ui-v2/assets/{schema-form-input-string-format-datetime-BhL8C5NS.js → schema-form-input-string-format-datetime-l3xt3PWf.js} +4 -4
- prefect/server/ui-v2/assets/{schema-form-input-string-format-datetime-BhL8C5NS.js.map → schema-form-input-string-format-datetime-l3xt3PWf.js.map} +1 -1
- prefect/server/ui-v2/assets/settings-txD0dR5Q.js +2 -0
- prefect/server/ui-v2/assets/settings-txD0dR5Q.js.map +1 -0
- prefect/server/ui-v2/assets/{sort-filter-BD4vwJXt.js → sort-filter-DHPFdKZ2.js} +2 -2
- prefect/server/ui-v2/assets/{sort-filter-BD4vwJXt.js.map → sort-filter-DHPFdKZ2.js.map} +1 -1
- prefect/server/ui-v2/assets/state-colors-CAAf0Eg3.js +2 -0
- prefect/server/ui-v2/assets/state-colors-CAAf0Eg3.js.map +1 -0
- prefect/server/ui-v2/assets/table-ULfpXJXB.js +2 -0
- prefect/server/ui-v2/assets/table-ULfpXJXB.js.map +1 -0
- prefect/server/ui-v2/assets/tags-input-BLzMOTDb.js +2 -0
- prefect/server/ui-v2/assets/tags-input-BLzMOTDb.js.map +1 -0
- prefect/server/ui-v2/assets/task-run-concurrency-limits-reset-dialog-BpeKHk7g.js +2 -0
- prefect/server/ui-v2/assets/{task-run-concurrency-limits-reset-dialog-CG3den1B.js.map → task-run-concurrency-limits-reset-dialog-BpeKHk7g.js.map} +1 -1
- prefect/server/ui-v2/assets/{task-run._id-DOcIzVi0.js → task-run._id-CkOl9MJs.js} +3 -3
- prefect/server/ui-v2/assets/{task-run._id-DOcIzVi0.js.map → task-run._id-CkOl9MJs.js.map} +1 -1
- prefect/server/ui-v2/assets/task-run._id-udkz1lhh.js +2 -0
- prefect/server/ui-v2/assets/{task-run._id-CnIVqU6v.js.map → task-run._id-udkz1lhh.js.map} +1 -1
- prefect/server/ui-v2/assets/task-runs-pagination-B7D5K_FM.js +2 -0
- prefect/server/ui-v2/assets/{task-runs-pagination-DLSAz-Ur.js.map → task-runs-pagination-B7D5K_FM.js.map} +1 -1
- prefect/server/ui-v2/assets/{textarea-D8LjlIx7.js → textarea-C4bdj7Jk.js} +2 -2
- prefect/server/ui-v2/assets/{textarea-D8LjlIx7.js.map → textarea-C4bdj7Jk.js.map} +1 -1
- prefect/server/ui-v2/assets/{timezone-select-BG3cL3-U.js → timezone-select-AdlSRQxZ.js} +2 -2
- prefect/server/ui-v2/assets/{timezone-select-BG3cL3-U.js.map → timezone-select-AdlSRQxZ.js.map} +1 -1
- prefect/server/ui-v2/assets/{toggle-group-D3zeurIL.js → toggle-group-C-vxYz4l.js} +2 -2
- prefect/server/ui-v2/assets/{toggle-group-D3zeurIL.js.map → toggle-group-C-vxYz4l.js.map} +1 -1
- prefect/server/ui-v2/assets/use-delete-automation-confirmation-dialog-Cqhaqtqe.js +2 -0
- prefect/server/ui-v2/assets/{use-delete-automation-confirmation-dialog-Bzy2ML2T.js.map → use-delete-automation-confirmation-dialog-Cqhaqtqe.js.map} +1 -1
- prefect/server/ui-v2/assets/{use-delete-block-document-confirmation-dialog-DRAP-Tnu.js → use-delete-block-document-confirmation-dialog-GjNhFxZe.js} +2 -2
- prefect/server/ui-v2/assets/{use-delete-block-document-confirmation-dialog-DRAP-Tnu.js.map → use-delete-block-document-confirmation-dialog-GjNhFxZe.js.map} +1 -1
- prefect/server/ui-v2/assets/{use-flow-runs-selected-rows-D4yWonR8.js → use-flow-runs-selected-rows-DfwmswyR.js} +2 -2
- prefect/server/ui-v2/assets/{use-flow-runs-selected-rows-D4yWonR8.js.map → use-flow-runs-selected-rows-DfwmswyR.js.map} +1 -1
- prefect/server/ui-v2/assets/{use-get-artifacts-flow-task-runs-DZeBiVd9.js → use-get-artifacts-flow-task-runs-BEBpG_5J.js} +2 -2
- prefect/server/ui-v2/assets/{use-get-artifacts-flow-task-runs-DZeBiVd9.js.map → use-get-artifacts-flow-task-runs-BEBpG_5J.js.map} +1 -1
- prefect/server/ui-v2/assets/{use-quick-run-BxAMqZDM.js → use-quick-run-BYBRcDwC.js} +2 -2
- prefect/server/ui-v2/assets/{use-quick-run-BxAMqZDM.js.map → use-quick-run-BYBRcDwC.js.map} +1 -1
- prefect/server/ui-v2/assets/{use-stepper-T3wAKNvM.js → use-stepper-Bk97vOTm.js} +2 -2
- prefect/server/ui-v2/assets/{use-stepper-T3wAKNvM.js.map → use-stepper-Bk97vOTm.js.map} +1 -1
- prefect/server/ui-v2/assets/{utilities-B2JMf8iI.js → utilities-BQwGFLk5.js} +2 -2
- prefect/server/ui-v2/assets/{utilities-B2JMf8iI.js.map → utilities-BQwGFLk5.js.map} +1 -1
- prefect/server/ui-v2/assets/{work-pool-filter-CZz0AJlt.js → work-pool-filter-Ddhp_M-L.js} +2 -2
- prefect/server/ui-v2/assets/{work-pool-filter-CZz0AJlt.js.map → work-pool-filter-Ddhp_M-L.js.map} +1 -1
- prefect/server/ui-v2/assets/work-pool-queue-toggle-DX3eV3R_.js +2 -0
- prefect/server/ui-v2/assets/{work-pool-queue-toggle-D4eeo-hi.js.map → work-pool-queue-toggle-DX3eV3R_.js.map} +1 -1
- prefect/server/ui-v2/assets/{work-pool._workPoolName-DrWddu9K.js → work-pool._workPoolName-BaRIsXBX.js} +2 -2
- prefect/server/ui-v2/assets/{work-pool._workPoolName-DrWddu9K.js.map → work-pool._workPoolName-BaRIsXBX.js.map} +1 -1
- prefect/server/ui-v2/assets/work-pool_._workPoolName.edit-dhS_Xz32.js +2 -0
- prefect/server/ui-v2/assets/{work-pool_._workPoolName.edit-CA0ePjCk.js.map → work-pool_._workPoolName.edit-dhS_Xz32.js.map} +1 -1
- prefect/server/ui-v2/assets/{work-pool_._workPoolName.queue._workQueueName-DpUnE86v.js → work-pool_._workPoolName.queue._workQueueName-BlstL9Se.js} +2 -2
- prefect/server/ui-v2/assets/{work-pool_._workPoolName.queue._workQueueName-DpUnE86v.js.map → work-pool_._workPoolName.queue._workQueueName-BlstL9Se.js.map} +1 -1
- prefect/server/ui-v2/assets/{work-queue-icon-text-BjiA7vAW.js → work-queue-icon-text-_Ez8e2dw.js} +2 -2
- prefect/server/ui-v2/assets/{work-queue-icon-text-BjiA7vAW.js.map → work-queue-icon-text-_Ez8e2dw.js.map} +1 -1
- prefect/server/ui-v2/index.html +2 -2
- prefect/settings/models/flows.py +14 -1
- prefect/settings/models/runner.py +16 -6
- prefect/task_engine.py +17 -3
- prefect/task_worker.py +99 -17
- prefect/tasks.py +2 -2
- prefect/testing/fixtures.py +41 -6
- {prefect-3.6.12.dist-info → prefect-3.6.13.dist-info}/METADATA +1 -1
- {prefect-3.6.12.dist-info → prefect-3.6.13.dist-info}/RECORD +221 -220
- prefect/server/ui-v2/assets/automation._id-IHh5QJwE.js +0 -2
- prefect/server/ui-v2/assets/block-type-logo-B31LJm5H.js +0 -2
- prefect/server/ui-v2/assets/block_._id.edit-CIhIBJrm.js +0 -2
- prefect/server/ui-v2/assets/catalog-CfniU0UV.js +0 -2
- prefect/server/ui-v2/assets/catalog_._slug-C9p86T4s.js +0 -2
- prefect/server/ui-v2/assets/catalog_._slug_.create-BhSunL__.js +0 -2
- prefect/server/ui-v2/assets/delete-confirmation-dialog-COdZmNfa.js +0 -2
- prefect/server/ui-v2/assets/deployment._id-DVmqclRz.js +0 -2
- prefect/server/ui-v2/assets/deployment_._id.duplicate-DVMjpk5m.js +0 -2
- prefect/server/ui-v2/assets/deployment_._id.edit-Ck_P6KDn.js +0 -2
- prefect/server/ui-v2/assets/flow-run-graph-CfoPEAgQ.js +0 -2
- prefect/server/ui-v2/assets/flow-run-graph-CfoPEAgQ.js.map +0 -1
- prefect/server/ui-v2/assets/flow-run._id-C-qxwEBp.js +0 -4
- prefect/server/ui-v2/assets/flow-run._id-C-qxwEBp.js.map +0 -1
- prefect/server/ui-v2/assets/flow-run._id-OL0YhyLW.js +0 -2
- prefect/server/ui-v2/assets/flow-runs-pagination-DnwkJapB.js +0 -2
- prefect/server/ui-v2/assets/flow-runs-pagination-DnwkJapB.js.map +0 -1
- prefect/server/ui-v2/assets/flow._id-DhrCicwR.js +0 -2
- prefect/server/ui-v2/assets/index-7-r4ia_S.js +0 -2
- prefect/server/ui-v2/assets/index-7ThYp9SY.js +0 -2
- prefect/server/ui-v2/assets/index-B7zHzWQW.css +0 -1
- prefect/server/ui-v2/assets/index-B_3f8Hcb.js +0 -2
- prefect/server/ui-v2/assets/index-BiCd-Iuz.js +0 -2
- prefect/server/ui-v2/assets/index-BiCd-Iuz.js.map +0 -1
- prefect/server/ui-v2/assets/index-CT_nG86y.js +0 -2
- prefect/server/ui-v2/assets/index-Cs8eFQKw.js +0 -2
- prefect/server/ui-v2/assets/index-H6bwm6L6.js +0 -2
- prefect/server/ui-v2/assets/index-WYPZo52S.js +0 -17
- prefect/server/ui-v2/assets/index-WYPZo52S.js.map +0 -1
- prefect/server/ui-v2/assets/index-qPlIYf3i.js +0 -2
- prefect/server/ui-v2/assets/route-error-state-ALftyvGl.js +0 -2
- prefect/server/ui-v2/assets/settings-BL0X8cDU.js +0 -2
- prefect/server/ui-v2/assets/settings-BL0X8cDU.js.map +0 -1
- prefect/server/ui-v2/assets/table-CEAx-qHs.js +0 -2
- prefect/server/ui-v2/assets/table-CEAx-qHs.js.map +0 -1
- prefect/server/ui-v2/assets/tags-input-D1RJZEUA.js +0 -2
- prefect/server/ui-v2/assets/tags-input-D1RJZEUA.js.map +0 -1
- prefect/server/ui-v2/assets/task-run-concurrency-limits-reset-dialog-CG3den1B.js +0 -2
- prefect/server/ui-v2/assets/task-run._id-CnIVqU6v.js +0 -2
- prefect/server/ui-v2/assets/task-runs-pagination-DLSAz-Ur.js +0 -2
- prefect/server/ui-v2/assets/use-delete-automation-confirmation-dialog-Bzy2ML2T.js +0 -2
- prefect/server/ui-v2/assets/use-local-storage-CpxMp5wR.js +0 -2
- prefect/server/ui-v2/assets/use-local-storage-CpxMp5wR.js.map +0 -1
- prefect/server/ui-v2/assets/work-pool-queue-toggle-D4eeo-hi.js +0 -2
- prefect/server/ui-v2/assets/work-pool_._workPoolName.edit-CA0ePjCk.js +0 -2
- {prefect-3.6.12.dist-info → prefect-3.6.13.dist-info}/WHEEL +0 -0
- {prefect-3.6.12.dist-info → prefect-3.6.13.dist-info}/entry_points.txt +0 -0
- {prefect-3.6.12.dist-info → prefect-3.6.13.dist-info}/licenses/LICENSE +0 -0
prefect/_build_info.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Generated by versioningit
|
|
2
|
-
__version__ = "3.6.
|
|
3
|
-
__build_date__ = "2026-01-
|
|
4
|
-
__git_commit__ = "
|
|
2
|
+
__version__ = "3.6.13"
|
|
3
|
+
__build_date__ = "2026-01-23 03:49:46.368256+00:00"
|
|
4
|
+
__git_commit__ = "9eb03a74b91426b564a1aa313670eb08a8b9ece8"
|
|
5
5
|
__dirty__ = False
|
|
@@ -4,6 +4,24 @@ from typing import Any, Union
|
|
|
4
4
|
from prefect.filesystems import NullFileSystem, WritableFileSystem
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
def call_explicitly_sync_block_method(
|
|
8
|
+
block: Union[WritableFileSystem, NullFileSystem],
|
|
9
|
+
method: str,
|
|
10
|
+
args: tuple[Any, ...],
|
|
11
|
+
kwargs: dict[str, Any],
|
|
12
|
+
) -> Any:
|
|
13
|
+
"""
|
|
14
|
+
Call a block method synchronously.
|
|
15
|
+
|
|
16
|
+
TODO: remove this once we have explicit sync/async methods on all storage blocks
|
|
17
|
+
|
|
18
|
+
see https://github.com/PrefectHQ/prefect/issues/15008
|
|
19
|
+
"""
|
|
20
|
+
# Pass _sync=True to ensure we get synchronous execution even when called
|
|
21
|
+
# from an async context (e.g., within a sync flow running in an async test)
|
|
22
|
+
return getattr(block, method)(*args, _sync=True, **kwargs)
|
|
23
|
+
|
|
24
|
+
|
|
7
25
|
async def call_explicitly_async_block_method(
|
|
8
26
|
block: Union[WritableFileSystem, NullFileSystem],
|
|
9
27
|
method: str,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from urllib.parse import urlparse, urlunparse
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def strip_auth_from_url(url: str) -> str:
|
|
5
|
+
"""
|
|
6
|
+
Remove authentication credentials (username/password) from a URL.
|
|
7
|
+
|
|
8
|
+
Useful for sanitizing URLs before including them in error messages
|
|
9
|
+
or logs to avoid leaking secrets.
|
|
10
|
+
"""
|
|
11
|
+
parsed = urlparse(url)
|
|
12
|
+
|
|
13
|
+
if not parsed.hostname:
|
|
14
|
+
return url
|
|
15
|
+
|
|
16
|
+
netloc = parsed.hostname
|
|
17
|
+
if parsed.port:
|
|
18
|
+
netloc = f"{netloc}:{parsed.port}"
|
|
19
|
+
|
|
20
|
+
return urlunparse(
|
|
21
|
+
(
|
|
22
|
+
parsed.scheme,
|
|
23
|
+
netloc,
|
|
24
|
+
parsed.path,
|
|
25
|
+
parsed.params,
|
|
26
|
+
parsed.query,
|
|
27
|
+
parsed.fragment,
|
|
28
|
+
)
|
|
29
|
+
)
|
prefect/_states.py
CHANGED
|
@@ -58,10 +58,13 @@ def exception_to_crashed_state_sync(
|
|
|
58
58
|
# calling it from sync-only code raises an error. Since anyio cancellation
|
|
59
59
|
# exceptions can only occur in async contexts anyway, we can safely skip
|
|
60
60
|
# this check when no async backend is running.
|
|
61
|
+
# anyio 4.12+ raises anyio.NoEventLoopError, older versions raise
|
|
62
|
+
# sniffio.AsyncLibraryNotFoundError. Catch both for compatibility.
|
|
63
|
+
# TODO: remove sniffio handling once anyio lower bound is >=4.12.1
|
|
61
64
|
try:
|
|
62
65
|
cancelled_exc_class = anyio.get_cancelled_exc_class()
|
|
63
66
|
is_anyio_cancelled = isinstance(exc, cancelled_exc_class)
|
|
64
|
-
except sniffio.AsyncLibraryNotFoundError:
|
|
67
|
+
except (sniffio.AsyncLibraryNotFoundError, anyio.NoEventLoopError):
|
|
65
68
|
is_anyio_cancelled = False
|
|
66
69
|
|
|
67
70
|
if is_anyio_cancelled:
|
|
@@ -446,22 +446,22 @@ class ConcurrencyLimitClient(BaseClient):
|
|
|
446
446
|
existing_limit = None
|
|
447
447
|
|
|
448
448
|
if not existing_limit:
|
|
449
|
+
create_kwargs: dict[str, Any] = {"name": name, "limit": limit}
|
|
450
|
+
if slot_decay_per_second is not None:
|
|
451
|
+
create_kwargs["slot_decay_per_second"] = slot_decay_per_second
|
|
449
452
|
self.create_global_concurrency_limit(
|
|
450
|
-
GlobalConcurrencyLimitCreate(
|
|
451
|
-
name=name,
|
|
452
|
-
limit=limit,
|
|
453
|
-
slot_decay_per_second=slot_decay_per_second,
|
|
454
|
-
)
|
|
453
|
+
GlobalConcurrencyLimitCreate(**create_kwargs)
|
|
455
454
|
)
|
|
456
455
|
elif existing_limit.limit != limit or (
|
|
457
456
|
slot_decay_per_second is not None
|
|
458
457
|
and existing_limit.slot_decay_per_second != slot_decay_per_second
|
|
459
458
|
):
|
|
459
|
+
update_kwargs: dict[str, Any] = {"limit": limit}
|
|
460
|
+
if slot_decay_per_second is not None:
|
|
461
|
+
update_kwargs["slot_decay_per_second"] = slot_decay_per_second
|
|
460
462
|
self.update_global_concurrency_limit(
|
|
461
463
|
name,
|
|
462
|
-
GlobalConcurrencyLimitUpdate(
|
|
463
|
-
limit=limit, slot_decay_per_second=slot_decay_per_second
|
|
464
|
-
),
|
|
464
|
+
GlobalConcurrencyLimitUpdate(**update_kwargs),
|
|
465
465
|
)
|
|
466
466
|
|
|
467
467
|
def read_global_concurrency_limits(
|
|
@@ -908,22 +908,22 @@ class ConcurrencyLimitAsyncClient(BaseAsyncClient):
|
|
|
908
908
|
existing_limit = None
|
|
909
909
|
|
|
910
910
|
if not existing_limit:
|
|
911
|
+
create_kwargs: dict[str, Any] = {"name": name, "limit": limit}
|
|
912
|
+
if slot_decay_per_second is not None:
|
|
913
|
+
create_kwargs["slot_decay_per_second"] = slot_decay_per_second
|
|
911
914
|
await self.create_global_concurrency_limit(
|
|
912
|
-
GlobalConcurrencyLimitCreate(
|
|
913
|
-
name=name,
|
|
914
|
-
limit=limit,
|
|
915
|
-
slot_decay_per_second=slot_decay_per_second,
|
|
916
|
-
)
|
|
915
|
+
GlobalConcurrencyLimitCreate(**create_kwargs)
|
|
917
916
|
)
|
|
918
917
|
elif existing_limit.limit != limit or (
|
|
919
918
|
slot_decay_per_second is not None
|
|
920
919
|
and existing_limit.slot_decay_per_second != slot_decay_per_second
|
|
921
920
|
):
|
|
921
|
+
update_kwargs: dict[str, Any] = {"limit": limit}
|
|
922
|
+
if slot_decay_per_second is not None:
|
|
923
|
+
update_kwargs["slot_decay_per_second"] = slot_decay_per_second
|
|
922
924
|
await self.update_global_concurrency_limit(
|
|
923
925
|
name,
|
|
924
|
-
GlobalConcurrencyLimitUpdate(
|
|
925
|
-
limit=limit, slot_decay_per_second=slot_decay_per_second
|
|
926
|
-
),
|
|
926
|
+
GlobalConcurrencyLimitUpdate(**update_kwargs),
|
|
927
927
|
)
|
|
928
928
|
|
|
929
929
|
async def read_global_concurrency_limits(
|
|
@@ -281,6 +281,27 @@ class FlowRunFilterIdempotencyKey(PrefectBaseModel):
|
|
|
281
281
|
)
|
|
282
282
|
|
|
283
283
|
|
|
284
|
+
class FlowRunFilterCreatedBy(PrefectBaseModel, OperatorMixin):
|
|
285
|
+
"""Filter by `FlowRun.created_by`."""
|
|
286
|
+
|
|
287
|
+
id_: Optional[List[UUID]] = Field(
|
|
288
|
+
default=None,
|
|
289
|
+
description="A list of creator IDs to include",
|
|
290
|
+
)
|
|
291
|
+
type_: Optional[List[str]] = Field(
|
|
292
|
+
default=None,
|
|
293
|
+
description=(
|
|
294
|
+
"A list of creator types to include. For example, 'DEPLOYMENT' for "
|
|
295
|
+
"scheduled runs or 'AUTOMATION' for runs triggered by automations."
|
|
296
|
+
),
|
|
297
|
+
examples=[["DEPLOYMENT", "AUTOMATION"]],
|
|
298
|
+
)
|
|
299
|
+
is_null_: Optional[bool] = Field(
|
|
300
|
+
default=None,
|
|
301
|
+
description="If true, only include flow runs without a creator",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
284
305
|
class FlowRunFilter(PrefectBaseModel, OperatorMixin):
|
|
285
306
|
"""Filter flow runs. Only flow runs matching all criteria will be returned"""
|
|
286
307
|
|
|
@@ -324,6 +345,9 @@ class FlowRunFilter(PrefectBaseModel, OperatorMixin):
|
|
|
324
345
|
idempotency_key: Optional[FlowRunFilterIdempotencyKey] = Field(
|
|
325
346
|
default=None, description="Filter criteria for `FlowRun.idempotency_key`"
|
|
326
347
|
)
|
|
348
|
+
created_by: Optional[FlowRunFilterCreatedBy] = Field(
|
|
349
|
+
default=None, description="Filter criteria for `FlowRun.created_by`"
|
|
350
|
+
)
|
|
327
351
|
|
|
328
352
|
|
|
329
353
|
class TaskRunFilterFlowRunId(PrefectBaseModel):
|
prefect/client/subscriptions.py
CHANGED
|
@@ -90,7 +90,7 @@ class Subscription(Generic[S]):
|
|
|
90
90
|
)
|
|
91
91
|
|
|
92
92
|
auth: dict[str, Any] = orjson.loads(await websocket.recv())
|
|
93
|
-
assert auth["type"] == "auth_success", auth.get("
|
|
93
|
+
assert auth["type"] == "auth_success", auth.get("reason", "")
|
|
94
94
|
|
|
95
95
|
message: dict[str, Any] = {"type": "subscribe", "keys": self.keys}
|
|
96
96
|
if self.client_id:
|
prefect/events/clients.py
CHANGED
|
@@ -73,6 +73,17 @@ if TYPE_CHECKING:
|
|
|
73
73
|
|
|
74
74
|
logger: "logging.Logger" = get_logger(__name__)
|
|
75
75
|
|
|
76
|
+
# Exceptions that indicate transient network issues and should trigger retries.
|
|
77
|
+
# These are used consistently across all event client retry loops.
|
|
78
|
+
# - ConnectionClosed: WebSocket connection was closed (e.g., server restart, load balancer timeout)
|
|
79
|
+
# - TimeoutError: Connection or operation timed out
|
|
80
|
+
# - OSError: Network-level errors (connection refused, DNS failures, network unreachable, etc.)
|
|
81
|
+
RETRYABLE_EXCEPTIONS: Tuple[Type[Exception], ...] = (
|
|
82
|
+
ConnectionClosed,
|
|
83
|
+
TimeoutError,
|
|
84
|
+
OSError,
|
|
85
|
+
)
|
|
86
|
+
|
|
76
87
|
|
|
77
88
|
def http_to_ws(url: str) -> str:
|
|
78
89
|
return url.replace("https://", "wss://").replace("http://", "ws://").rstrip("/")
|
|
@@ -278,14 +289,30 @@ class PrefectEventsClient(EventsClient):
|
|
|
278
289
|
self._checkpoint_every = checkpoint_every
|
|
279
290
|
|
|
280
291
|
async def __aenter__(self) -> Self:
|
|
281
|
-
# Don't handle any errors in the initial connection, because these are most
|
|
282
|
-
# likely a permission or configuration issue that should propagate
|
|
283
292
|
await super().__aenter__()
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
293
|
+
# Ensure at least one connection attempt even if reconnection_attempts is negative
|
|
294
|
+
max_attempts = max(1, self._reconnection_attempts + 1)
|
|
295
|
+
for i in range(max_attempts):
|
|
296
|
+
try:
|
|
297
|
+
await self._reconnect()
|
|
298
|
+
break
|
|
299
|
+
except RETRYABLE_EXCEPTIONS as e:
|
|
300
|
+
logger.debug(
|
|
301
|
+
"Initial connection attempt %s/%s failed: %s",
|
|
302
|
+
i + 1,
|
|
303
|
+
max_attempts,
|
|
304
|
+
str(e),
|
|
305
|
+
)
|
|
306
|
+
if i == max_attempts - 1:
|
|
307
|
+
self._log_connection_error(e)
|
|
308
|
+
raise
|
|
309
|
+
if i > 2:
|
|
310
|
+
await asyncio.sleep(1)
|
|
311
|
+
except Exception as e:
|
|
312
|
+
# Non-retryable exceptions (config/permission issues) should
|
|
313
|
+
# propagate immediately with a warning
|
|
314
|
+
self._log_connection_error(e)
|
|
315
|
+
raise
|
|
289
316
|
return self
|
|
290
317
|
|
|
291
318
|
async def __aexit__(
|
|
@@ -295,7 +322,13 @@ class PrefectEventsClient(EventsClient):
|
|
|
295
322
|
exc_tb: Optional[TracebackType],
|
|
296
323
|
) -> None:
|
|
297
324
|
self._websocket = None
|
|
298
|
-
|
|
325
|
+
# Only call __aexit__ on the connection if it was successfully established.
|
|
326
|
+
# The websockets library sets the "connection" attribute on the connect
|
|
327
|
+
# object only after __aenter__() completes successfully. Without this guard,
|
|
328
|
+
# we would get an AttributeError when cleaning up a connection that failed
|
|
329
|
+
# during establishment.
|
|
330
|
+
if hasattr(self._connect, "connection"):
|
|
331
|
+
await self._connect.__aexit__(exc_type, exc_val, exc_tb)
|
|
299
332
|
return await super().__aexit__(exc_type, exc_val, exc_tb)
|
|
300
333
|
|
|
301
334
|
def _log_debug(self, message: str, *args: Any, **kwargs: Any) -> None:
|
|
@@ -402,8 +435,8 @@ class PrefectEventsClient(EventsClient):
|
|
|
402
435
|
await self._checkpoint()
|
|
403
436
|
|
|
404
437
|
return
|
|
405
|
-
except
|
|
406
|
-
self._log_debug("Got
|
|
438
|
+
except RETRYABLE_EXCEPTIONS as e:
|
|
439
|
+
self._log_debug("Got retryable error: %s", type(e).__name__)
|
|
407
440
|
if i == self._reconnection_attempts:
|
|
408
441
|
# this was our final chance, log warning and raise
|
|
409
442
|
self._log_connection_error(e)
|
|
@@ -643,7 +676,13 @@ class PrefectEventSubscriber:
|
|
|
643
676
|
exc_tb: Optional[TracebackType],
|
|
644
677
|
) -> None:
|
|
645
678
|
self._websocket = None
|
|
646
|
-
|
|
679
|
+
# Only call __aexit__ on the connection if it was successfully established.
|
|
680
|
+
# The websockets library sets the "connection" attribute on the connect
|
|
681
|
+
# object only after __aenter__() completes successfully. Without this guard,
|
|
682
|
+
# we would get an AttributeError when cleaning up a connection that failed
|
|
683
|
+
# during establishment.
|
|
684
|
+
if hasattr(self._connect, "connection"):
|
|
685
|
+
await self._connect.__aexit__(exc_type, exc_val, exc_tb)
|
|
647
686
|
|
|
648
687
|
def __aiter__(self) -> Self:
|
|
649
688
|
return self
|
|
@@ -681,9 +720,9 @@ class PrefectEventSubscriber:
|
|
|
681
720
|
except ConnectionClosedOK:
|
|
682
721
|
logger.debug('Connection closed with "OK" status')
|
|
683
722
|
raise StopAsyncIteration
|
|
684
|
-
except
|
|
723
|
+
except RETRYABLE_EXCEPTIONS:
|
|
685
724
|
logger.debug(
|
|
686
|
-
"
|
|
725
|
+
"Retryable error with %s/%s attempts",
|
|
687
726
|
i + 1,
|
|
688
727
|
self._reconnection_attempts,
|
|
689
728
|
)
|
prefect/flow_engine.py
CHANGED
|
@@ -5,6 +5,7 @@ import logging
|
|
|
5
5
|
import multiprocessing
|
|
6
6
|
import multiprocessing.context
|
|
7
7
|
import os
|
|
8
|
+
import threading
|
|
8
9
|
import time
|
|
9
10
|
from contextlib import (
|
|
10
11
|
AsyncExitStack,
|
|
@@ -36,7 +37,7 @@ from anyio import CancelScope
|
|
|
36
37
|
from opentelemetry import propagate, trace
|
|
37
38
|
from typing_extensions import ParamSpec
|
|
38
39
|
|
|
39
|
-
from prefect import Task
|
|
40
|
+
from prefect import Task, __version__
|
|
40
41
|
from prefect.client.orchestration import PrefectClient, SyncPrefectClient, get_client
|
|
41
42
|
from prefect.client.schemas import FlowRun, TaskRun
|
|
42
43
|
from prefect.client.schemas.filters import FlowRunFilter
|
|
@@ -60,6 +61,8 @@ from prefect.context import (
|
|
|
60
61
|
serialize_context,
|
|
61
62
|
)
|
|
62
63
|
from prefect.engine import handle_engine_signals
|
|
64
|
+
from prefect.events.related import RelatedResource, tags_as_related_resources
|
|
65
|
+
from prefect.events.utilities import emit_event
|
|
63
66
|
from prefect.exceptions import (
|
|
64
67
|
Abort,
|
|
65
68
|
MissingFlowError,
|
|
@@ -161,6 +164,129 @@ def load_flow_and_flow_run(flow_run_id: UUID) -> tuple[FlowRun, Flow[..., Any]]:
|
|
|
161
164
|
return flow_run, flow
|
|
162
165
|
|
|
163
166
|
|
|
167
|
+
@contextmanager
|
|
168
|
+
def send_heartbeats_sync(
|
|
169
|
+
engine: "FlowRunEngine[Any, Any]",
|
|
170
|
+
) -> Generator[None, None, None]:
|
|
171
|
+
"""Context manager that maintains heartbeats for a sync flow run.
|
|
172
|
+
|
|
173
|
+
Heartbeats are emitted at regular intervals while the flow is running.
|
|
174
|
+
The loop checks the flow run state before each heartbeat and stops
|
|
175
|
+
if the run reaches a terminal state.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
engine: The FlowRunEngine instance to emit heartbeats for.
|
|
179
|
+
|
|
180
|
+
Yields:
|
|
181
|
+
None
|
|
182
|
+
"""
|
|
183
|
+
heartbeat_seconds = engine.heartbeat_seconds
|
|
184
|
+
if heartbeat_seconds is None:
|
|
185
|
+
yield
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
stop_event = threading.Event()
|
|
189
|
+
|
|
190
|
+
def heartbeat_loop() -> None:
|
|
191
|
+
while not stop_event.is_set():
|
|
192
|
+
# Check state before emitting - don't emit if final
|
|
193
|
+
if (
|
|
194
|
+
engine.flow_run
|
|
195
|
+
and engine.flow_run.state
|
|
196
|
+
and engine.flow_run.state.is_final()
|
|
197
|
+
):
|
|
198
|
+
engine.logger.debug("Flow run in terminal state, stopping heartbeat")
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
engine._emit_flow_run_heartbeat()
|
|
203
|
+
except Exception:
|
|
204
|
+
engine.logger.debug("Failed to emit heartbeat", exc_info=True)
|
|
205
|
+
|
|
206
|
+
# Sleep in increments to allow quick shutdown
|
|
207
|
+
for _ in range(heartbeat_seconds):
|
|
208
|
+
if stop_event.is_set():
|
|
209
|
+
return
|
|
210
|
+
time.sleep(1)
|
|
211
|
+
|
|
212
|
+
thread = threading.Thread(target=heartbeat_loop, daemon=True)
|
|
213
|
+
thread.start()
|
|
214
|
+
engine.logger.debug("Started flow run heartbeat context")
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
yield
|
|
218
|
+
finally:
|
|
219
|
+
stop_event.set()
|
|
220
|
+
thread.join(timeout=2)
|
|
221
|
+
engine.logger.debug("Stopped flow run heartbeat context")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@asynccontextmanager
|
|
225
|
+
async def send_heartbeats_async(
|
|
226
|
+
engine: "AsyncFlowRunEngine[Any, Any]",
|
|
227
|
+
) -> AsyncGenerator[None, None]:
|
|
228
|
+
"""Async context manager that maintains heartbeats for an async flow run.
|
|
229
|
+
|
|
230
|
+
Heartbeats are emitted at regular intervals while the flow is running.
|
|
231
|
+
The loop checks the flow run state before each heartbeat and stops
|
|
232
|
+
if the run reaches a terminal state.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
engine: The AsyncFlowRunEngine instance to emit heartbeats for.
|
|
236
|
+
|
|
237
|
+
Yields:
|
|
238
|
+
None
|
|
239
|
+
"""
|
|
240
|
+
heartbeat_seconds = engine.heartbeat_seconds
|
|
241
|
+
if heartbeat_seconds is None:
|
|
242
|
+
yield
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
stop_flag = False
|
|
246
|
+
|
|
247
|
+
async def heartbeat_loop() -> None:
|
|
248
|
+
nonlocal stop_flag
|
|
249
|
+
try:
|
|
250
|
+
while not stop_flag:
|
|
251
|
+
# Check state before emitting - don't emit if final
|
|
252
|
+
if (
|
|
253
|
+
engine.flow_run
|
|
254
|
+
and engine.flow_run.state
|
|
255
|
+
and engine.flow_run.state.is_final()
|
|
256
|
+
):
|
|
257
|
+
engine.logger.debug(
|
|
258
|
+
"Flow run in terminal state, stopping heartbeat"
|
|
259
|
+
)
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
engine._emit_flow_run_heartbeat()
|
|
264
|
+
except Exception:
|
|
265
|
+
engine.logger.debug("Failed to emit heartbeat", exc_info=True)
|
|
266
|
+
|
|
267
|
+
# Sleep in increments to allow quick shutdown (parity with sync version)
|
|
268
|
+
for _ in range(heartbeat_seconds):
|
|
269
|
+
if stop_flag:
|
|
270
|
+
return
|
|
271
|
+
await asyncio.sleep(1)
|
|
272
|
+
except asyncio.CancelledError:
|
|
273
|
+
engine.logger.debug("Heartbeat loop cancelled")
|
|
274
|
+
|
|
275
|
+
task = asyncio.create_task(heartbeat_loop())
|
|
276
|
+
engine.logger.debug("Started flow run heartbeat context")
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
yield
|
|
280
|
+
finally:
|
|
281
|
+
stop_flag = True
|
|
282
|
+
task.cancel()
|
|
283
|
+
try:
|
|
284
|
+
await task
|
|
285
|
+
except asyncio.CancelledError:
|
|
286
|
+
pass
|
|
287
|
+
engine.logger.debug("Stopped flow run heartbeat context")
|
|
288
|
+
|
|
289
|
+
|
|
164
290
|
@dataclass
|
|
165
291
|
class BaseFlowRunEngine(Generic[P, R]):
|
|
166
292
|
flow: Union[Flow[P, R], Flow[P, Coroutine[Any, Any, R]]]
|
|
@@ -200,10 +326,59 @@ class BaseFlowRunEngine(Generic[P, R]):
|
|
|
200
326
|
return False # TODO: handle this differently?
|
|
201
327
|
return getattr(self, "flow_run").state.is_pending()
|
|
202
328
|
|
|
329
|
+
@property
|
|
330
|
+
def heartbeat_seconds(self) -> Optional[int]:
|
|
331
|
+
"""Get the heartbeat interval from settings."""
|
|
332
|
+
return get_current_settings().flows.heartbeat_frequency
|
|
333
|
+
|
|
203
334
|
def cancel_all_tasks(self) -> None:
|
|
204
335
|
if hasattr(self.flow.task_runner, "cancel_all"):
|
|
205
336
|
self.flow.task_runner.cancel_all() # type: ignore
|
|
206
337
|
|
|
338
|
+
def _emit_flow_run_heartbeat(self) -> None:
|
|
339
|
+
"""Emit a heartbeat event for the current flow run."""
|
|
340
|
+
if not self.flow_run:
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
related: list[RelatedResource] = []
|
|
344
|
+
tags: list[str] = list(self.flow_run.tags or [])
|
|
345
|
+
|
|
346
|
+
# Add flow as related resource using flow_id for consistency with other events
|
|
347
|
+
if self.flow_run.flow_id:
|
|
348
|
+
related.append(
|
|
349
|
+
RelatedResource.model_validate(
|
|
350
|
+
{
|
|
351
|
+
"prefect.resource.id": f"prefect.flow.{self.flow_run.flow_id}",
|
|
352
|
+
"prefect.resource.role": "flow",
|
|
353
|
+
"prefect.resource.name": self.flow.name if self.flow else "",
|
|
354
|
+
}
|
|
355
|
+
)
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# Add deployment as related resource if available
|
|
359
|
+
# Note: deployment name is not available on flow_run without an API call
|
|
360
|
+
if self.flow_run.deployment_id:
|
|
361
|
+
related.append(
|
|
362
|
+
RelatedResource.model_validate(
|
|
363
|
+
{
|
|
364
|
+
"prefect.resource.id": f"prefect.deployment.{self.flow_run.deployment_id}",
|
|
365
|
+
"prefect.resource.role": "deployment",
|
|
366
|
+
}
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
related += tags_as_related_resources(set(tags))
|
|
371
|
+
|
|
372
|
+
emit_event(
|
|
373
|
+
event="prefect.flow-run.heartbeat",
|
|
374
|
+
resource={
|
|
375
|
+
"prefect.resource.id": f"prefect.flow-run.{self.flow_run.id}",
|
|
376
|
+
"prefect.resource.name": self.flow_run.name or "",
|
|
377
|
+
"prefect.version": __version__,
|
|
378
|
+
},
|
|
379
|
+
related=related,
|
|
380
|
+
)
|
|
381
|
+
|
|
207
382
|
def _update_otel_labels(
|
|
208
383
|
self, span: trace.Span, client: Union[SyncPrefectClient, PrefectClient]
|
|
209
384
|
):
|
|
@@ -343,6 +518,7 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
|
343
518
|
|
|
344
519
|
self._telemetry.update_state(state)
|
|
345
520
|
self.call_hooks(state)
|
|
521
|
+
|
|
346
522
|
return state
|
|
347
523
|
|
|
348
524
|
def result(self, raise_on_failure: bool = True) -> "Union[R, State, None]":
|
|
@@ -791,10 +967,11 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
|
791
967
|
seconds=self.flow.timeout_seconds,
|
|
792
968
|
timeout_exc_type=FlowRunTimeoutError,
|
|
793
969
|
):
|
|
794
|
-
self
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
970
|
+
with send_heartbeats_sync(self):
|
|
971
|
+
self.logger.debug(
|
|
972
|
+
f"Executing flow {self.flow.name!r} for flow run {self.flow_run.name!r}..."
|
|
973
|
+
)
|
|
974
|
+
yield self
|
|
798
975
|
except TimeoutError as exc:
|
|
799
976
|
self.handle_timeout(exc)
|
|
800
977
|
except Exception as exc:
|
|
@@ -930,6 +1107,7 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
|
930
1107
|
|
|
931
1108
|
self._telemetry.update_state(state)
|
|
932
1109
|
await self.call_hooks(state)
|
|
1110
|
+
|
|
933
1111
|
return state
|
|
934
1112
|
|
|
935
1113
|
async def result(self, raise_on_failure: bool = True) -> "Union[R, State, None]":
|
|
@@ -1381,10 +1559,11 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
|
1381
1559
|
seconds=self.flow.timeout_seconds,
|
|
1382
1560
|
timeout_exc_type=FlowRunTimeoutError,
|
|
1383
1561
|
):
|
|
1384
|
-
self
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1562
|
+
async with send_heartbeats_async(self):
|
|
1563
|
+
self.logger.debug(
|
|
1564
|
+
f"Executing flow {self.flow.name!r} for flow run {self.flow_run.name!r}..."
|
|
1565
|
+
)
|
|
1566
|
+
yield self
|
|
1388
1567
|
except TimeoutError as exc:
|
|
1389
1568
|
await self.handle_timeout(exc)
|
|
1390
1569
|
except Exception as exc:
|
|
@@ -1614,6 +1793,7 @@ def run_flow_in_subprocess(
|
|
|
1614
1793
|
parameters: dict[str, Any] | None = None,
|
|
1615
1794
|
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
|
1616
1795
|
context: dict[str, Any] | None = None,
|
|
1796
|
+
env: dict[str, str] | None = None,
|
|
1617
1797
|
) -> multiprocessing.context.SpawnProcess:
|
|
1618
1798
|
"""
|
|
1619
1799
|
Run a flow in a subprocess.
|
|
@@ -1630,6 +1810,7 @@ def run_flow_in_subprocess(
|
|
|
1630
1810
|
the current context will be used. A serialized context should be provided if
|
|
1631
1811
|
this function is called in a separate memory space from the parent run (e.g.
|
|
1632
1812
|
in a subprocess or on another machine).
|
|
1813
|
+
env: Additional environment variables to set in the subprocess.
|
|
1633
1814
|
|
|
1634
1815
|
Returns:
|
|
1635
1816
|
A multiprocessing.context.SpawnProcess representing the process that is running the flow.
|
|
@@ -1672,7 +1853,8 @@ def run_flow_in_subprocess(
|
|
|
1672
1853
|
| {
|
|
1673
1854
|
# TODO: make this a thing we can pass into the engine
|
|
1674
1855
|
"PREFECT__ENABLE_CANCELLATION_AND_CRASHED_HOOKS": "false",
|
|
1675
|
-
}
|
|
1856
|
+
}
|
|
1857
|
+
| (env or {}),
|
|
1676
1858
|
flow=flow,
|
|
1677
1859
|
flow_run=flow_run,
|
|
1678
1860
|
parameters=parameters,
|
prefect/flows.py
CHANGED
|
@@ -1717,8 +1717,7 @@ class Flow(Generic[P, R]):
|
|
|
1717
1717
|
return_type=return_type,
|
|
1718
1718
|
)
|
|
1719
1719
|
|
|
1720
|
-
|
|
1721
|
-
async def visualize(self, *args: "P.args", **kwargs: "P.kwargs"):
|
|
1720
|
+
async def avisualize(self, *args: "P.args", **kwargs: "P.kwargs") -> None:
|
|
1722
1721
|
"""
|
|
1723
1722
|
Generates a graphviz object representing the current flow. In IPython notebooks,
|
|
1724
1723
|
it's rendered inline, otherwise in a new window as a PNG.
|
|
@@ -1776,6 +1775,66 @@ class Flow(Generic[P, R]):
|
|
|
1776
1775
|
new_exception.__traceback__ = e.__traceback__
|
|
1777
1776
|
raise new_exception
|
|
1778
1777
|
|
|
1778
|
+
@async_dispatch(avisualize)
|
|
1779
|
+
def visualize(self, *args: "P.args", **kwargs: "P.kwargs") -> None:
|
|
1780
|
+
"""
|
|
1781
|
+
Generates a graphviz object representing the current flow. In IPython notebooks,
|
|
1782
|
+
it's rendered inline, otherwise in a new window as a PNG.
|
|
1783
|
+
|
|
1784
|
+
Raises:
|
|
1785
|
+
- ImportError: If `graphviz` isn't installed.
|
|
1786
|
+
- GraphvizExecutableNotFoundError: If the `dot` executable isn't found.
|
|
1787
|
+
- FlowVisualizationError: If the flow can't be visualized for any other reason.
|
|
1788
|
+
"""
|
|
1789
|
+
from prefect.utilities.visualization import (
|
|
1790
|
+
FlowVisualizationError,
|
|
1791
|
+
GraphvizExecutableNotFoundError,
|
|
1792
|
+
GraphvizImportError,
|
|
1793
|
+
TaskVizTracker,
|
|
1794
|
+
VisualizationUnsupportedError,
|
|
1795
|
+
build_task_dependencies,
|
|
1796
|
+
visualize_task_dependencies,
|
|
1797
|
+
)
|
|
1798
|
+
|
|
1799
|
+
if not PREFECT_TESTING_UNIT_TEST_MODE:
|
|
1800
|
+
warnings.warn(
|
|
1801
|
+
"`flow.visualize()` will execute code inside of your flow that is not"
|
|
1802
|
+
" decorated with `@task` or `@flow`."
|
|
1803
|
+
)
|
|
1804
|
+
|
|
1805
|
+
try:
|
|
1806
|
+
with TaskVizTracker() as tracker:
|
|
1807
|
+
if self.isasync:
|
|
1808
|
+
# Run async flow via event loop
|
|
1809
|
+
run_coro_as_sync(self.fn(*args, **kwargs))
|
|
1810
|
+
else:
|
|
1811
|
+
self.fn(*args, **kwargs)
|
|
1812
|
+
|
|
1813
|
+
graph = build_task_dependencies(tracker)
|
|
1814
|
+
|
|
1815
|
+
visualize_task_dependencies(graph, self.name)
|
|
1816
|
+
|
|
1817
|
+
except GraphvizImportError:
|
|
1818
|
+
raise
|
|
1819
|
+
except GraphvizExecutableNotFoundError:
|
|
1820
|
+
raise
|
|
1821
|
+
except VisualizationUnsupportedError:
|
|
1822
|
+
raise
|
|
1823
|
+
except FlowVisualizationError:
|
|
1824
|
+
raise
|
|
1825
|
+
except Exception as e:
|
|
1826
|
+
msg = (
|
|
1827
|
+
"It's possible you are trying to visualize a flow that contains "
|
|
1828
|
+
"code that directly interacts with the result of a task"
|
|
1829
|
+
" inside of the flow. \nTry passing a `viz_return_value` "
|
|
1830
|
+
"to the task decorator, e.g. `@task(viz_return_value=[1, 2, 3]).`"
|
|
1831
|
+
)
|
|
1832
|
+
|
|
1833
|
+
new_exception = type(e)(str(e) + "\n" + msg)
|
|
1834
|
+
# Copy traceback information from the original exception
|
|
1835
|
+
new_exception.__traceback__ = e.__traceback__
|
|
1836
|
+
raise new_exception
|
|
1837
|
+
|
|
1779
1838
|
|
|
1780
1839
|
class FlowDecorator:
|
|
1781
1840
|
@overload
|