prefect 3.6.15__py3-none-any.whl → 3.6.16.dev3__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 (259) hide show
  1. prefect/__init__.py +20 -0
  2. prefect/_build_info.py +3 -3
  3. prefect/_internal/analytics/__init__.py +80 -0
  4. prefect/_internal/analytics/_config.py +2 -0
  5. prefect/_internal/analytics/ci_detection.py +39 -0
  6. prefect/_internal/analytics/client.py +132 -0
  7. prefect/_internal/analytics/device_id.py +55 -0
  8. prefect/_internal/analytics/emit.py +90 -0
  9. prefect/_internal/analytics/enabled.py +33 -0
  10. prefect/_internal/analytics/events.py +14 -0
  11. prefect/_internal/analytics/milestones.py +177 -0
  12. prefect/_internal/analytics/notice.py +60 -0
  13. prefect/_internal/analytics/service.py +238 -0
  14. prefect/analytics/__init__.py +67 -0
  15. prefect/deployments/runner.py +20 -0
  16. prefect/flow_engine.py +16 -0
  17. prefect/flows.py +8 -0
  18. prefect/logging/formatters.py +24 -1
  19. prefect/server/ui-v2/assets/{artifact-card-BBzQR-Cj.js → artifact-card-BclosG8t.js} +2 -2
  20. prefect/server/ui-v2/assets/{artifact-card-BBzQR-Cj.js.map → artifact-card-BclosG8t.js.map} +1 -1
  21. prefect/server/ui-v2/assets/artifact._id-C0r4BJr2.js +2 -0
  22. prefect/server/ui-v2/assets/artifact._id-C0r4BJr2.js.map +1 -0
  23. prefect/server/ui-v2/assets/{automation-wizard-EYSfXlQ7.js → automation-wizard-Bmgt6nn8.js} +2 -2
  24. prefect/server/ui-v2/assets/{automation-wizard-EYSfXlQ7.js.map → automation-wizard-Bmgt6nn8.js.map} +1 -1
  25. prefect/server/ui-v2/assets/automation._id-Ccke3KJz.js +2 -0
  26. prefect/server/ui-v2/assets/{automation._id-Dn7jABys.js.map → automation._id-Ccke3KJz.js.map} +1 -1
  27. prefect/server/ui-v2/assets/{automation_._id.edit-COrqswD3.js → automation_._id.edit-BlKPUTVf.js} +2 -2
  28. prefect/server/ui-v2/assets/{automation_._id.edit-COrqswD3.js.map → automation_._id.edit-BlKPUTVf.js.map} +1 -1
  29. prefect/server/ui-v2/assets/{automations-header-DN_dmMXW.js → automations-header-Df2zw3SC.js} +2 -2
  30. prefect/server/ui-v2/assets/{automations-header-DN_dmMXW.js.map → automations-header-Df2zw3SC.js.map} +1 -1
  31. prefect/server/ui-v2/assets/{base-job-template-form-section-De2ACYbw.js → base-job-template-form-section-oZD6U-RW.js} +2 -2
  32. prefect/server/ui-v2/assets/{base-job-template-form-section-De2ACYbw.js.map → base-job-template-form-section-oZD6U-RW.js.map} +1 -1
  33. prefect/server/ui-v2/assets/{block-type-details-Cklc7TkK.js → block-type-details-CYrdaLot.js} +2 -2
  34. prefect/server/ui-v2/assets/{block-type-details-Cklc7TkK.js.map → block-type-details-CYrdaLot.js.map} +1 -1
  35. prefect/server/ui-v2/assets/block-type-logo-CyAWq7mC.js +2 -0
  36. prefect/server/ui-v2/assets/block-type-logo-CyAWq7mC.js.map +1 -0
  37. prefect/server/ui-v2/assets/{block._id-tuz1Bcj2.js → block._id-CBgHrpTP.js} +2 -2
  38. prefect/server/ui-v2/assets/{block._id-tuz1Bcj2.js.map → block._id-CBgHrpTP.js.map} +1 -1
  39. prefect/server/ui-v2/assets/{block_._id.edit-CFEYhF5R.js → block_._id.edit-CW-kdi4O.js} +2 -2
  40. prefect/server/ui-v2/assets/{block_._id.edit-CFEYhF5R.js.map → block_._id.edit-CW-kdi4O.js.map} +1 -1
  41. prefect/server/ui-v2/assets/{catalog-CCPRo7nY.js → catalog-Brh0kn1I.js} +2 -2
  42. prefect/server/ui-v2/assets/{catalog-CCPRo7nY.js.map → catalog-Brh0kn1I.js.map} +1 -1
  43. prefect/server/ui-v2/assets/catalog_._slug-MCgzguIZ.js +2 -0
  44. prefect/server/ui-v2/assets/{catalog_._slug-Bl2J2oI3.js.map → catalog_._slug-MCgzguIZ.js.map} +1 -1
  45. prefect/server/ui-v2/assets/{catalog_._slug_.create-9UD_SxDN.js → catalog_._slug_.create-DBaeHi9Q.js} +2 -2
  46. prefect/server/ui-v2/assets/{catalog_._slug_.create-9UD_SxDN.js.map → catalog_._slug_.create-DBaeHi9Q.js.map} +1 -1
  47. prefect/server/ui-v2/assets/{collapsible-BVQTBG_U.js → collapsible-B_NJS0uJ.js} +2 -2
  48. prefect/server/ui-v2/assets/{collapsible-BVQTBG_U.js.map → collapsible-B_NJS0uJ.js.map} +1 -1
  49. prefect/server/ui-v2/assets/{concurrency-limit._id-CsgRxA0a.js → concurrency-limit._id-qzNUQzQa.js} +2 -2
  50. prefect/server/ui-v2/assets/{concurrency-limit._id-CsgRxA0a.js.map → concurrency-limit._id-qzNUQzQa.js.map} +1 -1
  51. prefect/server/ui-v2/assets/create-BwXG6EIb.js +2 -0
  52. prefect/server/ui-v2/assets/{create-D--LHaji.js.map → create-BwXG6EIb.js.map} +1 -1
  53. prefect/server/ui-v2/assets/{create-BycfZHGQ.js → create-MoiIE9Ms.js} +2 -2
  54. prefect/server/ui-v2/assets/{create-BycfZHGQ.js.map → create-MoiIE9Ms.js.map} +1 -1
  55. prefect/server/ui-v2/assets/data-table-CPkppyg6.js +2 -0
  56. prefect/server/ui-v2/assets/data-table-CPkppyg6.js.map +1 -0
  57. prefect/server/ui-v2/assets/delete-confirmation-dialog-CwDK_2QS.js +2 -0
  58. prefect/server/ui-v2/assets/{delete-confirmation-dialog-DHkm2p8m.js.map → delete-confirmation-dialog-CwDK_2QS.js.map} +1 -1
  59. prefect/server/ui-v2/assets/{deployment-action-header-D5GOooPm.js → deployment-action-header-Vp9YsCJO.js} +2 -2
  60. prefect/server/ui-v2/assets/{deployment-action-header-D5GOooPm.js.map → deployment-action-header-Vp9YsCJO.js.map} +1 -1
  61. prefect/server/ui-v2/assets/{deployment-form-wfg6HeAJ.js → deployment-form-CJcg_6DJ.js} +3 -3
  62. prefect/server/ui-v2/assets/{deployment-form-wfg6HeAJ.js.map → deployment-form-CJcg_6DJ.js.map} +1 -1
  63. prefect/server/ui-v2/assets/{deployment-links-6iugOgDh.js → deployment-links-CIkJIuHh.js} +2 -2
  64. prefect/server/ui-v2/assets/{deployment-links-6iugOgDh.js.map → deployment-links-CIkJIuHh.js.map} +1 -1
  65. prefect/server/ui-v2/assets/deployment._id-C6XrGMp_.js +2 -0
  66. prefect/server/ui-v2/assets/{deployment._id-Ci9zge5n.js.map → deployment._id-C6XrGMp_.js.map} +1 -1
  67. prefect/server/ui-v2/assets/{deployment._id-whsGcHdu.js → deployment._id-CWgTYmTG.js} +2 -2
  68. prefect/server/ui-v2/assets/{deployment._id-whsGcHdu.js.map → deployment._id-CWgTYmTG.js.map} +1 -1
  69. prefect/server/ui-v2/assets/deployment_._id.duplicate-Bg0s0ZJn.js +2 -0
  70. prefect/server/ui-v2/assets/{deployment_._id.duplicate-D9ZWkh8H.js.map → deployment_._id.duplicate-Bg0s0ZJn.js.map} +1 -1
  71. prefect/server/ui-v2/assets/deployment_._id.edit-CnoB0UeG.js +2 -0
  72. prefect/server/ui-v2/assets/{deployment_._id.edit-CS7sObzC.js.map → deployment_._id.edit-CnoB0UeG.js.map} +1 -1
  73. prefect/server/ui-v2/assets/{deployment_._id.run-g7SZuDFi.js → deployment_._id.run-BqPK6rTB.js} +2 -2
  74. prefect/server/ui-v2/assets/{deployment_._id.run-g7SZuDFi.js.map → deployment_._id.run-BqPK6rTB.js.map} +1 -1
  75. prefect/server/ui-v2/assets/{dropdown-menu-0atiOSFo.js → dropdown-menu-D7Gwbd15.js} +2 -2
  76. prefect/server/ui-v2/assets/{dropdown-menu-0atiOSFo.js.map → dropdown-menu-D7Gwbd15.js.map} +1 -1
  77. prefect/server/ui-v2/assets/{event-resource-display-DHiAb1Up.js → event-resource-display-CDRgVKd4.js} +2 -2
  78. prefect/server/ui-v2/assets/{event-resource-display-DHiAb1Up.js.map → event-resource-display-CDRgVKd4.js.map} +1 -1
  79. prefect/server/ui-v2/assets/event._eventDate._eventId-C73GMDkK.js +2 -0
  80. prefect/server/ui-v2/assets/{event._eventDate._eventId-D7Twzb22.js.map → event._eventDate._eventId-C73GMDkK.js.map} +1 -1
  81. prefect/server/ui-v2/assets/{flow-run-graph-BhzuSJLm.js → flow-run-graph-ckiE3mA9.js} +2 -2
  82. prefect/server/ui-v2/assets/{flow-run-graph-BhzuSJLm.js.map → flow-run-graph-ckiE3mA9.js.map} +1 -1
  83. prefect/server/ui-v2/assets/{flow-run._id-CqpH6bvx.js → flow-run._id-BukrNCAq.js} +2 -2
  84. prefect/server/ui-v2/assets/{flow-run._id-CqpH6bvx.js.map → flow-run._id-BukrNCAq.js.map} +1 -1
  85. prefect/server/ui-v2/assets/{flow-run._id-lIIQBD7H.js → flow-run._id-CtI5VZ2M.js} +2 -2
  86. prefect/server/ui-v2/assets/{flow-run._id-lIIQBD7H.js.map → flow-run._id-CtI5VZ2M.js.map} +1 -1
  87. prefect/server/ui-v2/assets/flow-run._id-s1UJuakA.js +4 -0
  88. prefect/server/ui-v2/assets/{flow-run._id-RLy2kBh0.js.map → flow-run._id-s1UJuakA.js.map} +1 -1
  89. prefect/server/ui-v2/assets/{flow-runs-pagination-BATUweRV.js → flow-runs-pagination-SLbYtvaR.js} +2 -2
  90. prefect/server/ui-v2/assets/{flow-runs-pagination-BATUweRV.js.map → flow-runs-pagination-SLbYtvaR.js.map} +1 -1
  91. prefect/server/ui-v2/assets/flow._id-CZ5P0v4N.js +2 -0
  92. prefect/server/ui-v2/assets/{flow._id-BdQGWPKP.js.map → flow._id-CZ5P0v4N.js.map} +1 -1
  93. prefect/server/ui-v2/assets/{form-DLrbC7rV.js → form-mws4law8.js} +2 -2
  94. prefect/server/ui-v2/assets/{form-DLrbC7rV.js.map → form-mws4law8.js.map} +1 -1
  95. prefect/server/ui-v2/assets/{header-BTAWyeXv.js → header-Bes1LmY7.js} +2 -2
  96. prefect/server/ui-v2/assets/{header-BTAWyeXv.js.map → header-Bes1LmY7.js.map} +1 -1
  97. prefect/server/ui-v2/assets/{header-ChFAx5uR.js → header-Bj_zBR-a.js} +2 -2
  98. prefect/server/ui-v2/assets/{header-ChFAx5uR.js.map → header-Bj_zBR-a.js.map} +1 -1
  99. prefect/server/ui-v2/assets/{header-CyQqOj1w.js → header-CPxTHSvz.js} +2 -2
  100. prefect/server/ui-v2/assets/{header-CyQqOj1w.js.map → header-CPxTHSvz.js.map} +1 -1
  101. prefect/server/ui-v2/assets/{index-m48E4KcG.js → index-B8vo2Lrg.js} +5 -5
  102. prefect/server/ui-v2/assets/{index-m48E4KcG.js.map → index-B8vo2Lrg.js.map} +1 -1
  103. prefect/server/ui-v2/assets/{index-D5RT-SAp.js → index-BBeh-3Nh.js} +2 -2
  104. prefect/server/ui-v2/assets/{index-D5RT-SAp.js.map → index-BBeh-3Nh.js.map} +1 -1
  105. prefect/server/ui-v2/assets/index-BLnEvHia.js +2 -0
  106. prefect/server/ui-v2/assets/{index-7i4X9TeK.js.map → index-BLnEvHia.js.map} +1 -1
  107. prefect/server/ui-v2/assets/index-BpKoubXu.js +2 -0
  108. prefect/server/ui-v2/assets/index-BpKoubXu.js.map +1 -0
  109. prefect/server/ui-v2/assets/index-By6YGqR3.js +2 -0
  110. prefect/server/ui-v2/assets/index-By6YGqR3.js.map +1 -0
  111. prefect/server/ui-v2/assets/index-CEi3BlKI.js +2 -0
  112. prefect/server/ui-v2/assets/index-CEi3BlKI.js.map +1 -0
  113. prefect/server/ui-v2/assets/{index-BaWDC4da.js → index-CS-zxv7Z.js} +2 -2
  114. prefect/server/ui-v2/assets/{index-BaWDC4da.js.map → index-CS-zxv7Z.js.map} +1 -1
  115. prefect/server/ui-v2/assets/{index-kP_I5Qfd.js → index-CToHKh4q.js} +2 -2
  116. prefect/server/ui-v2/assets/{index-kP_I5Qfd.js.map → index-CToHKh4q.js.map} +1 -1
  117. prefect/server/ui-v2/assets/{index-CKGj5lSu.js → index-CUVvZndW.js} +2 -2
  118. prefect/server/ui-v2/assets/{index-CKGj5lSu.js.map → index-CUVvZndW.js.map} +1 -1
  119. prefect/server/ui-v2/assets/{index-CxUfZRkN.js → index-CYVn-I3i.js} +2 -2
  120. prefect/server/ui-v2/assets/{index-CxUfZRkN.js.map → index-CYVn-I3i.js.map} +1 -1
  121. prefect/server/ui-v2/assets/{index-CnlN-VC_.js → index-Cun0JN4t.js} +2 -2
  122. prefect/server/ui-v2/assets/{index-CnlN-VC_.js.map → index-Cun0JN4t.js.map} +1 -1
  123. prefect/server/ui-v2/assets/{index-BwCLKpNb.js → index-D6ynV6U7.js} +2 -2
  124. prefect/server/ui-v2/assets/{index-BwCLKpNb.js.map → index-D6ynV6U7.js.map} +1 -1
  125. prefect/server/ui-v2/assets/{index-CHf6xi9j.js → index-DdNUJLRW.js} +2 -2
  126. prefect/server/ui-v2/assets/{index-CHf6xi9j.js.map → index-DdNUJLRW.js.map} +1 -1
  127. prefect/server/ui-v2/assets/{index-CQnWiUE5.js → index-DlHOXQhu.js} +2 -2
  128. prefect/server/ui-v2/assets/{index-CQnWiUE5.js.map → index-DlHOXQhu.js.map} +1 -1
  129. prefect/server/ui-v2/assets/{index-Eqdr6Ff7.js → index-DyVw8YE8.js} +2 -2
  130. prefect/server/ui-v2/assets/{index-Eqdr6Ff7.js.map → index-DyVw8YE8.js.map} +1 -1
  131. prefect/server/ui-v2/assets/{index-Btb_-PDp.js → index-DzRz7D2P.js} +2 -2
  132. prefect/server/ui-v2/assets/{index-Btb_-PDp.js.map → index-DzRz7D2P.js.map} +1 -1
  133. prefect/server/ui-v2/assets/{index-DQGif3jm.js → index-DzUcVNZg.js} +2 -2
  134. prefect/server/ui-v2/assets/{index-DQGif3jm.js.map → index-DzUcVNZg.js.map} +1 -1
  135. prefect/server/ui-v2/assets/{index-DNh9ZBy4.js → index-IBvMMs6S.js} +2 -2
  136. prefect/server/ui-v2/assets/{index-DNh9ZBy4.js.map → index-IBvMMs6S.js.map} +1 -1
  137. prefect/server/ui-v2/assets/index-_kpA__te.js +2 -0
  138. prefect/server/ui-v2/assets/{index-CiSFHkhI.js.map → index-_kpA__te.js.map} +1 -1
  139. prefect/server/ui-v2/assets/index-m9O-nIOl.css +1 -0
  140. prefect/server/ui-v2/assets/{index-lyQav_XM.js → index-wOvyf10b.js} +2 -2
  141. prefect/server/ui-v2/assets/{index-lyQav_XM.js.map → index-wOvyf10b.js.map} +1 -1
  142. prefect/server/ui-v2/assets/{json-input-Ba-BeBcw.js → json-input-Ce-HlRqa.js} +2 -2
  143. prefect/server/ui-v2/assets/{json-input-Ba-BeBcw.js.map → json-input-Ce-HlRqa.js.map} +1 -1
  144. prefect/server/ui-v2/assets/{key-value-DkSKn6jE.js → key-value-XHEZOtZX.js} +2 -2
  145. prefect/server/ui-v2/assets/{key-value-DkSKn6jE.js.map → key-value-XHEZOtZX.js.map} +1 -1
  146. prefect/server/ui-v2/assets/{key._key-Cjx57ymg.js → key._key-LpsMf3zD.js} +2 -2
  147. prefect/server/ui-v2/assets/{key._key-Cjx57ymg.js.map → key._key-LpsMf3zD.js.map} +1 -1
  148. prefect/server/ui-v2/assets/{lazy-markdown-Du2Xu3Yp.js → lazy-markdown-1Hz0xzca.js} +2 -2
  149. prefect/server/ui-v2/assets/{lazy-markdown-Du2Xu3Yp.js.map → lazy-markdown-1Hz0xzca.js.map} +1 -1
  150. prefect/server/ui-v2/assets/{login-DxMF9Jvj.js → login-D5uepl9L.js} +2 -2
  151. prefect/server/ui-v2/assets/{login-DxMF9Jvj.js.map → login-D5uepl9L.js.map} +1 -1
  152. prefect/server/ui-v2/assets/{markdown-input-DPi8zyRr.js → markdown-input-CG6M9zD0.js} +2 -2
  153. prefect/server/ui-v2/assets/{markdown-input-DPi8zyRr.js.map → markdown-input-CG6M9zD0.js.map} +1 -1
  154. prefect/server/ui-v2/assets/python-example-snippet-DRHcUlCX.js +3 -0
  155. prefect/server/ui-v2/assets/{python-example-snippet-DfCFYPN_.js.map → python-example-snippet-DRHcUlCX.js.map} +1 -1
  156. prefect/server/ui-v2/assets/{python-input-mr6GD840.js → python-input-BsUS8fw1.js} +2 -2
  157. prefect/server/ui-v2/assets/{python-input-mr6GD840.js.map → python-input-BsUS8fw1.js.map} +1 -1
  158. prefect/server/ui-v2/assets/{radio-group-Co3cN0Kv.js → radio-group-BUsmwdrt.js} +2 -2
  159. prefect/server/ui-v2/assets/{radio-group-Co3cN0Kv.js.map → radio-group-BUsmwdrt.js.map} +1 -1
  160. prefect/server/ui-v2/assets/{route-error-state-DnBaNT2T.js → route-error-state-BFBpiIhD.js} +2 -2
  161. prefect/server/ui-v2/assets/{route-error-state-DnBaNT2T.js.map → route-error-state-BFBpiIhD.js.map} +1 -1
  162. prefect/server/ui-v2/assets/{schema-form-CWDHSME9.js → schema-form-BwTmkvJk.js} +2 -2
  163. prefect/server/ui-v2/assets/schema-form-BwTmkvJk.js.map +1 -0
  164. prefect/server/ui-v2/assets/{schema-form-input-string-format-datetime-BSWlW_aQ.js → schema-form-input-string-format-datetime-nrb3g-JJ.js} +4 -4
  165. prefect/server/ui-v2/assets/{schema-form-input-string-format-datetime-BSWlW_aQ.js.map → schema-form-input-string-format-datetime-nrb3g-JJ.js.map} +1 -1
  166. prefect/server/ui-v2/assets/{settings-DU5y6tJE.js → settings-CUBtK5aW.js} +2 -2
  167. prefect/server/ui-v2/assets/{settings-DU5y6tJE.js.map → settings-CUBtK5aW.js.map} +1 -1
  168. prefect/server/ui-v2/assets/{sort-filter-DpuWNkQo.js → sort-filter-Cv8zAXMp.js} +2 -2
  169. prefect/server/ui-v2/assets/{sort-filter-DpuWNkQo.js.map → sort-filter-Cv8zAXMp.js.map} +1 -1
  170. prefect/server/ui-v2/assets/{table-bBL6C0ZK.js → table-BBNGfPra.js} +2 -2
  171. prefect/server/ui-v2/assets/{table-bBL6C0ZK.js.map → table-BBNGfPra.js.map} +1 -1
  172. prefect/server/ui-v2/assets/{tags-input-BLPMVWpZ.js → tags-input-DHrnkwjQ.js} +2 -2
  173. prefect/server/ui-v2/assets/{tags-input-BLPMVWpZ.js.map → tags-input-DHrnkwjQ.js.map} +1 -1
  174. prefect/server/ui-v2/assets/task-run-concurrency-limits-reset-dialog-Z8-yFZ2_.js +2 -0
  175. prefect/server/ui-v2/assets/{task-run-concurrency-limits-reset-dialog-S69sih55.js.map → task-run-concurrency-limits-reset-dialog-Z8-yFZ2_.js.map} +1 -1
  176. prefect/server/ui-v2/assets/{task-run._id-Dl3Azs8t.js → task-run._id-BY58gqs2.js} +2 -2
  177. prefect/server/ui-v2/assets/{task-run._id-Dl3Azs8t.js.map → task-run._id-BY58gqs2.js.map} +1 -1
  178. prefect/server/ui-v2/assets/{task-run._id-B_tIDm4K.js → task-run._id-u73Jxfqw.js} +3 -3
  179. prefect/server/ui-v2/assets/{task-run._id-B_tIDm4K.js.map → task-run._id-u73Jxfqw.js.map} +1 -1
  180. prefect/server/ui-v2/assets/task-runs-pagination-DUy3147A.js +2 -0
  181. prefect/server/ui-v2/assets/{task-runs-pagination-VdR-LO4r.js.map → task-runs-pagination-DUy3147A.js.map} +1 -1
  182. prefect/server/ui-v2/assets/{textarea-B3_TtPRi.js → textarea-DUhh6-kq.js} +2 -2
  183. prefect/server/ui-v2/assets/{textarea-B3_TtPRi.js.map → textarea-DUhh6-kq.js.map} +1 -1
  184. prefect/server/ui-v2/assets/timezone-select-D4Q6VBtD.js +2 -0
  185. prefect/server/ui-v2/assets/{timezone-select-BuPYznge.js.map → timezone-select-D4Q6VBtD.js.map} +1 -1
  186. prefect/server/ui-v2/assets/{toggle-group-Cn07UIim.js → toggle-group-7WUJn2Tx.js} +2 -2
  187. prefect/server/ui-v2/assets/{toggle-group-Cn07UIim.js.map → toggle-group-7WUJn2Tx.js.map} +1 -1
  188. prefect/server/ui-v2/assets/use-delete-automation-confirmation-dialog-D4AD5Ndf.js +2 -0
  189. prefect/server/ui-v2/assets/{use-delete-automation-confirmation-dialog-DpdsLH4t.js.map → use-delete-automation-confirmation-dialog-D4AD5Ndf.js.map} +1 -1
  190. prefect/server/ui-v2/assets/{use-delete-block-document-confirmation-dialog-Cq5nGhzr.js → use-delete-block-document-confirmation-dialog-DnwWEGUY.js} +2 -2
  191. prefect/server/ui-v2/assets/{use-delete-block-document-confirmation-dialog-Cq5nGhzr.js.map → use-delete-block-document-confirmation-dialog-DnwWEGUY.js.map} +1 -1
  192. prefect/server/ui-v2/assets/{use-flow-runs-selected-rows-DmQSWBTu.js → use-flow-runs-selected-rows-C-tehPRB.js} +2 -2
  193. prefect/server/ui-v2/assets/{use-flow-runs-selected-rows-DmQSWBTu.js.map → use-flow-runs-selected-rows-C-tehPRB.js.map} +1 -1
  194. prefect/server/ui-v2/assets/{use-get-artifacts-flow-task-runs-DTwcWiqW.js → use-get-artifacts-flow-task-runs-DRWRrmrb.js} +2 -2
  195. prefect/server/ui-v2/assets/{use-get-artifacts-flow-task-runs-DTwcWiqW.js.map → use-get-artifacts-flow-task-runs-DRWRrmrb.js.map} +1 -1
  196. prefect/server/ui-v2/assets/{use-quick-run-CPempwLh.js → use-quick-run-B4Xj-YQQ.js} +2 -2
  197. prefect/server/ui-v2/assets/{use-quick-run-CPempwLh.js.map → use-quick-run-B4Xj-YQQ.js.map} +1 -1
  198. prefect/server/ui-v2/assets/use-state-favicon-au2TZdMV.js +2 -0
  199. prefect/server/ui-v2/assets/{use-state-favicon-M09DmrNy.js.map → use-state-favicon-au2TZdMV.js.map} +1 -1
  200. prefect/server/ui-v2/assets/{use-stepper-CDucnvzp.js → use-stepper-DyIb3vlq.js} +2 -2
  201. prefect/server/ui-v2/assets/{use-stepper-CDucnvzp.js.map → use-stepper-DyIb3vlq.js.map} +1 -1
  202. prefect/server/ui-v2/assets/{utilities-BRCNRcQl.js → utilities-D9Y2wo66.js} +2 -2
  203. prefect/server/ui-v2/assets/{utilities-BRCNRcQl.js.map → utilities-D9Y2wo66.js.map} +1 -1
  204. prefect/server/ui-v2/assets/vendor-recharts-BvvJP9Po.js +34 -0
  205. prefect/server/ui-v2/assets/vendor-recharts-BvvJP9Po.js.map +1 -0
  206. prefect/server/ui-v2/assets/{work-pool-filter-C2tpSS-w.js → work-pool-filter-CSOATj2D.js} +2 -2
  207. prefect/server/ui-v2/assets/{work-pool-filter-C2tpSS-w.js.map → work-pool-filter-CSOATj2D.js.map} +1 -1
  208. prefect/server/ui-v2/assets/work-pool-queue-toggle-Dh7B1zMo.js +2 -0
  209. prefect/server/ui-v2/assets/{work-pool-queue-toggle-BQ9b-Wxy.js.map → work-pool-queue-toggle-Dh7B1zMo.js.map} +1 -1
  210. prefect/server/ui-v2/assets/work-pool._workPoolName-B_NFWXKS.js +2 -0
  211. prefect/server/ui-v2/assets/{work-pool._workPoolName-C_LiL3SE.js.map → work-pool._workPoolName-B_NFWXKS.js.map} +1 -1
  212. prefect/server/ui-v2/assets/{work-pool_._workPoolName.edit-DBHlrVVx.js → work-pool_._workPoolName.edit-Cyzn8g-S.js} +2 -2
  213. prefect/server/ui-v2/assets/{work-pool_._workPoolName.edit-DBHlrVVx.js.map → work-pool_._workPoolName.edit-Cyzn8g-S.js.map} +1 -1
  214. prefect/server/ui-v2/assets/{work-pool_._workPoolName.queue._workQueueName-DkITxJOP.js → work-pool_._workPoolName.queue._workQueueName-DQzqQWPT.js} +2 -2
  215. prefect/server/ui-v2/assets/{work-pool_._workPoolName.queue._workQueueName-DkITxJOP.js.map → work-pool_._workPoolName.queue._workQueueName-DQzqQWPT.js.map} +1 -1
  216. prefect/server/ui-v2/assets/{work-queue-icon-text-D4BWBVDN.js → work-queue-icon-text-BOpcJ8YU.js} +2 -2
  217. prefect/server/ui-v2/assets/{work-queue-icon-text-D4BWBVDN.js.map → work-queue-icon-text-BOpcJ8YU.js.map} +1 -1
  218. prefect/server/ui-v2/index.html +3 -3
  219. {prefect-3.6.15.dist-info → prefect-3.6.16.dev3.dist-info}/METADATA +2 -1
  220. {prefect-3.6.15.dist-info → prefect-3.6.16.dev3.dist-info}/RECORD +223 -211
  221. prefect/server/ui-v2/assets/artifact._id-BFdf1WUA.js +0 -2
  222. prefect/server/ui-v2/assets/artifact._id-BFdf1WUA.js.map +0 -1
  223. prefect/server/ui-v2/assets/automation._id-Dn7jABys.js +0 -2
  224. prefect/server/ui-v2/assets/block-type-logo-Cbpfxded.js +0 -2
  225. prefect/server/ui-v2/assets/block-type-logo-Cbpfxded.js.map +0 -1
  226. prefect/server/ui-v2/assets/catalog_._slug-Bl2J2oI3.js +0 -2
  227. prefect/server/ui-v2/assets/create-D--LHaji.js +0 -2
  228. prefect/server/ui-v2/assets/data-table-rlbkLcrY.js +0 -2
  229. prefect/server/ui-v2/assets/data-table-rlbkLcrY.js.map +0 -1
  230. prefect/server/ui-v2/assets/delete-confirmation-dialog-DHkm2p8m.js +0 -2
  231. prefect/server/ui-v2/assets/deployment._id-Ci9zge5n.js +0 -2
  232. prefect/server/ui-v2/assets/deployment_._id.duplicate-D9ZWkh8H.js +0 -2
  233. prefect/server/ui-v2/assets/deployment_._id.edit-CS7sObzC.js +0 -2
  234. prefect/server/ui-v2/assets/event._eventDate._eventId-D7Twzb22.js +0 -2
  235. prefect/server/ui-v2/assets/flow-run._id-RLy2kBh0.js +0 -4
  236. prefect/server/ui-v2/assets/flow._id-BdQGWPKP.js +0 -2
  237. prefect/server/ui-v2/assets/index-7i4X9TeK.js +0 -2
  238. prefect/server/ui-v2/assets/index-BkiGTHwU.js +0 -2
  239. prefect/server/ui-v2/assets/index-BkiGTHwU.js.map +0 -1
  240. prefect/server/ui-v2/assets/index-COouj_0-.css +0 -1
  241. prefect/server/ui-v2/assets/index-CiSFHkhI.js +0 -2
  242. prefect/server/ui-v2/assets/index-DATPYZfT.js +0 -2
  243. prefect/server/ui-v2/assets/index-DATPYZfT.js.map +0 -1
  244. prefect/server/ui-v2/assets/index-VH4TnZ4e.js +0 -2
  245. prefect/server/ui-v2/assets/index-VH4TnZ4e.js.map +0 -1
  246. prefect/server/ui-v2/assets/python-example-snippet-DfCFYPN_.js +0 -3
  247. prefect/server/ui-v2/assets/schema-form-CWDHSME9.js.map +0 -1
  248. prefect/server/ui-v2/assets/task-run-concurrency-limits-reset-dialog-S69sih55.js +0 -2
  249. prefect/server/ui-v2/assets/task-runs-pagination-VdR-LO4r.js +0 -2
  250. prefect/server/ui-v2/assets/timezone-select-BuPYznge.js +0 -2
  251. prefect/server/ui-v2/assets/use-delete-automation-confirmation-dialog-DpdsLH4t.js +0 -2
  252. prefect/server/ui-v2/assets/use-state-favicon-M09DmrNy.js +0 -2
  253. prefect/server/ui-v2/assets/vendor-recharts-BAN776s_.js +0 -34
  254. prefect/server/ui-v2/assets/vendor-recharts-BAN776s_.js.map +0 -1
  255. prefect/server/ui-v2/assets/work-pool-queue-toggle-BQ9b-Wxy.js +0 -2
  256. prefect/server/ui-v2/assets/work-pool._workPoolName-C_LiL3SE.js +0 -2
  257. {prefect-3.6.15.dist-info → prefect-3.6.16.dev3.dist-info}/WHEEL +0 -0
  258. {prefect-3.6.15.dist-info → prefect-3.6.16.dev3.dist-info}/entry_points.txt +0 -0
  259. {prefect-3.6.15.dist-info → prefect-3.6.16.dev3.dist-info}/licenses/LICENSE +0 -0
prefect/__init__.py CHANGED
@@ -130,6 +130,26 @@ def _initialize_plugins() -> None:
130
130
  # Initialize plugins on import if enabled
131
131
  _initialize_plugins()
132
132
 
133
+
134
+ def _initialize_sdk_analytics() -> None:
135
+ """
136
+ Initialize SDK analytics for telemetry.
137
+
138
+ This runs automatically when Prefect is imported. Errors are silently
139
+ ignored to ensure analytics never impacts normal Prefect operation.
140
+ """
141
+ try:
142
+ from prefect._internal.analytics import initialize_analytics
143
+
144
+ initialize_analytics()
145
+ except Exception:
146
+ # Never let analytics initialization impact Prefect
147
+ pass
148
+
149
+
150
+ # Initialize SDK analytics on import
151
+ _initialize_sdk_analytics()
152
+
133
153
  _public_api: dict[str, tuple[Optional[str], str]] = {
134
154
  "allow_failure": (__spec__.parent, ".main"),
135
155
  "apause_flow_run": (__spec__.parent, ".main"),
prefect/_build_info.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # Generated by versioningit
2
- __version__ = "3.6.15"
3
- __build_date__ = "2026-01-30 00:27:37.852288+00:00"
4
- __git_commit__ = "757a089bbd323e73c4437f77de86030f406f6734"
2
+ __version__ = "3.6.16.dev3"
3
+ __build_date__ = "2026-02-01 08:13:48.533566+00:00"
4
+ __git_commit__ = "d264f42f6e99ead48f89c9793129eef4b7875eec"
5
5
  __dirty__ = False
@@ -0,0 +1,80 @@
1
+ """
2
+ Internal implementation for SDK Analytics.
3
+
4
+ This module contains internal functions that should not be used directly.
5
+ Use the public API from prefect.analytics instead.
6
+ """
7
+
8
+ from prefect._internal.analytics.emit import (
9
+ _is_interactive_terminal,
10
+ emit_integration_event,
11
+ emit_sdk_event,
12
+ )
13
+ from prefect._internal.analytics.enabled import is_telemetry_enabled
14
+ from prefect._internal.analytics.milestones import (
15
+ _mark_existing_user_milestones,
16
+ try_mark_milestone,
17
+ )
18
+ from prefect._internal.analytics.notice import maybe_show_telemetry_notice
19
+
20
+ # Track initialization state
21
+ _telemetry_initialized = False
22
+
23
+
24
+ def initialize_analytics() -> None:
25
+ """
26
+ Initialize SDK analytics on Prefect import.
27
+
28
+ This function:
29
+ 1. Checks if telemetry is enabled (quick local checks only - non-blocking)
30
+ 2. Detects existing users and pre-marks their milestones (no events emitted)
31
+ 3. For new users: shows the first-run notice and emits first_sdk_import
32
+
33
+ Onboarding events are only emitted in interactive terminals to avoid
34
+ tracking deployed flow runs (e.g., Kubernetes jobs) as new users.
35
+
36
+ Called automatically when Prefect is imported.
37
+ """
38
+ global _telemetry_initialized
39
+
40
+ if _telemetry_initialized:
41
+ return
42
+
43
+ _telemetry_initialized = True
44
+
45
+ # Quick local checks only - no network calls
46
+ if not is_telemetry_enabled():
47
+ return
48
+
49
+ # Only emit onboarding events in interactive terminals
50
+ # This prevents deployed flow runs from being counted as new users
51
+ if not _is_interactive_terminal():
52
+ return
53
+
54
+ try:
55
+ # Check for existing users and pre-mark their milestones
56
+ # This must happen BEFORE any events are emitted
57
+ is_existing_user = _mark_existing_user_milestones()
58
+
59
+ # Don't emit onboarding events for existing users
60
+ if is_existing_user:
61
+ return
62
+
63
+ # Show first-run notice (only in interactive terminals)
64
+ maybe_show_telemetry_notice()
65
+
66
+ # Emit first_sdk_import event for new users only (tracked as a milestone
67
+ # so it is only emitted once per installation)
68
+ try_mark_milestone("first_sdk_import")
69
+ except Exception:
70
+ pass # Silently ignore initialization errors
71
+
72
+
73
+ __all__ = [
74
+ "initialize_analytics",
75
+ "is_telemetry_enabled",
76
+ "emit_sdk_event",
77
+ "emit_integration_event",
78
+ "try_mark_milestone",
79
+ "_is_interactive_terminal",
80
+ ]
@@ -0,0 +1,2 @@
1
+ # Generated at build time - DO NOT EDIT
2
+ AMPLITUDE_API_KEY = "c97dd2acbf306ab7bf54aca0aeb7ffa1"
@@ -0,0 +1,39 @@
1
+ """
2
+ CI environment detection for SDK telemetry.
3
+
4
+ Telemetry is automatically disabled in CI environments.
5
+ """
6
+
7
+ import os
8
+
9
+ # Common CI environment variables
10
+ CI_ENV_VARS = frozenset(
11
+ {
12
+ "CI",
13
+ "GITHUB_ACTIONS",
14
+ "GITLAB_CI",
15
+ "JENKINS_URL",
16
+ "TRAVIS",
17
+ "CIRCLECI",
18
+ "BUILDKITE",
19
+ "TF_BUILD", # Azure DevOps
20
+ "CODEBUILD_BUILD_ID", # AWS CodeBuild
21
+ "BITBUCKET_COMMIT",
22
+ "TEAMCITY_VERSION",
23
+ "DRONE",
24
+ "SEMAPHORE",
25
+ "APPVEYOR",
26
+ "BUDDY",
27
+ "CI_NAME", # Generic CI indicator
28
+ }
29
+ )
30
+
31
+
32
+ def is_ci_environment() -> bool:
33
+ """
34
+ Check if the current environment is a CI environment.
35
+
36
+ Returns:
37
+ True if any known CI environment variable is set
38
+ """
39
+ return any(os.environ.get(var) for var in CI_ENV_VARS)
@@ -0,0 +1,132 @@
1
+ """
2
+ Amplitude client wrapper for SDK telemetry.
3
+
4
+ Provides fire-and-forget event tracking with silent failure handling.
5
+ """
6
+
7
+ import atexit
8
+ import logging
9
+ import platform
10
+ from typing import Any
11
+
12
+ from amplitude import Amplitude, BaseEvent, Config
13
+
14
+ import prefect
15
+
16
+ # Amplitude API key for SDK telemetry
17
+ # This is a write-only key that can only send events, not read data
18
+ try:
19
+ from prefect._internal.analytics._config import AMPLITUDE_API_KEY
20
+ except ImportError:
21
+ AMPLITUDE_API_KEY = "YOUR_AMPLITUDE_API_KEY_HERE"
22
+
23
+ # Module-level client instance
24
+ _amplitude_client: Amplitude | None = None
25
+ _initialized = False
26
+
27
+
28
+ def _get_event_properties() -> dict[str, str]:
29
+ """Get common event properties included with all events."""
30
+ return {
31
+ "prefect_version": prefect.__version__,
32
+ "python_version": platform.python_version(),
33
+ "platform": platform.system(),
34
+ "architecture": platform.machine(),
35
+ }
36
+
37
+
38
+ def _initialize_client() -> bool:
39
+ """
40
+ Initialize the Amplitude client.
41
+
42
+ Returns:
43
+ True if initialization succeeded, False otherwise
44
+ """
45
+ global _amplitude_client, _initialized
46
+
47
+ if _initialized:
48
+ return _amplitude_client is not None
49
+
50
+ _initialized = True
51
+
52
+ if AMPLITUDE_API_KEY == "YOUR_AMPLITUDE_API_KEY_HERE":
53
+ # API key not configured - telemetry disabled
54
+ return False
55
+
56
+ try:
57
+ # Create a silent logger for Amplitude to prevent SDK errors from reaching users
58
+ amplitude_logger = logging.getLogger("amplitude")
59
+ amplitude_logger.setLevel(logging.CRITICAL)
60
+
61
+ config = Config(
62
+ # Flush events after a short delay to avoid blocking
63
+ flush_interval_millis=10000, # 10 seconds
64
+ flush_queue_size=10,
65
+ # Minimize network overhead
66
+ min_id_length=1,
67
+ )
68
+
69
+ _amplitude_client = Amplitude(AMPLITUDE_API_KEY, config)
70
+
71
+ # Register shutdown handler to flush remaining events
72
+ atexit.register(_shutdown_client)
73
+
74
+ return True
75
+ except Exception:
76
+ return False
77
+
78
+
79
+ def _shutdown_client() -> None:
80
+ """Shutdown the Amplitude client, flushing any remaining events."""
81
+ global _amplitude_client
82
+
83
+ if _amplitude_client is not None:
84
+ try:
85
+ _amplitude_client.shutdown()
86
+ except Exception:
87
+ pass
88
+ _amplitude_client = None
89
+
90
+
91
+ def track_event(
92
+ event_name: str,
93
+ device_id: str,
94
+ extra_properties: dict[str, Any] | None = None,
95
+ ) -> bool:
96
+ """
97
+ Track an event with Amplitude.
98
+
99
+ Args:
100
+ event_name: The name of the event to track
101
+ device_id: The anonymous device identifier
102
+ extra_properties: Additional event properties
103
+
104
+ Returns:
105
+ True if the event was tracked, False otherwise
106
+ """
107
+ properties = _get_event_properties()
108
+ if extra_properties:
109
+ properties.update(extra_properties)
110
+
111
+ if not _initialize_client():
112
+ return False
113
+
114
+ try:
115
+ event = BaseEvent(
116
+ event_type=event_name,
117
+ device_id=device_id,
118
+ event_properties=properties,
119
+ )
120
+ _amplitude_client.track(event)
121
+ return True
122
+ except Exception:
123
+ return False
124
+
125
+
126
+ def flush() -> None:
127
+ """Flush any pending events immediately."""
128
+ if _amplitude_client is not None:
129
+ try:
130
+ _amplitude_client.flush()
131
+ except Exception:
132
+ pass
@@ -0,0 +1,55 @@
1
+ """
2
+ Device ID generation and persistence for SDK telemetry.
3
+
4
+ The device ID is an anonymous identifier used to correlate events
5
+ from the same installation without identifying the user.
6
+ """
7
+
8
+ import os
9
+ from pathlib import Path
10
+ from uuid import uuid4
11
+
12
+ from prefect.settings import get_current_settings
13
+
14
+
15
+ def _get_device_id_path() -> Path:
16
+ """Get the path to the device ID file."""
17
+ settings = get_current_settings()
18
+ return settings.home / ".sdk_telemetry" / "device_id"
19
+
20
+
21
+ def get_or_create_device_id() -> str:
22
+ """
23
+ Get the existing device ID or create a new one.
24
+
25
+ The device ID is stored in $PREFECT_HOME/.sdk_telemetry/device_id
26
+
27
+ Returns:
28
+ A UUID string identifying this installation
29
+ """
30
+ device_id_path = _get_device_id_path()
31
+
32
+ # Try to read existing device ID
33
+ if device_id_path.exists():
34
+ try:
35
+ device_id = device_id_path.read_text().strip()
36
+ if device_id:
37
+ return device_id
38
+ except Exception:
39
+ pass
40
+
41
+ # Generate new device ID
42
+ device_id = str(uuid4())
43
+
44
+ # Persist device ID
45
+ try:
46
+ device_id_path.parent.mkdir(parents=True, exist_ok=True)
47
+ device_id_path.write_text(device_id)
48
+ # Set restrictive permissions (owner read/write only)
49
+ os.chmod(device_id_path, 0o600)
50
+ except Exception:
51
+ # If we can't persist, still return the generated ID
52
+ # (it will be regenerated next time)
53
+ pass
54
+
55
+ return device_id
@@ -0,0 +1,90 @@
1
+ """
2
+ Event emission for SDK analytics.
3
+ """
4
+
5
+ import sys
6
+ from typing import Any
7
+
8
+ from prefect._internal.analytics.device_id import get_or_create_device_id
9
+ from prefect._internal.analytics.events import SDKEvent
10
+ from prefect._internal.analytics.service import AnalyticsEvent, AnalyticsService
11
+
12
+
13
+ def _is_interactive_terminal() -> bool:
14
+ """Check if we're running in an interactive terminal."""
15
+ try:
16
+ return sys.stdout.isatty() or sys.stderr.isatty()
17
+ except Exception:
18
+ return False
19
+
20
+
21
+ def emit_sdk_event(
22
+ event_name: SDKEvent,
23
+ extra_properties: dict[str, Any] | None = None,
24
+ ) -> bool:
25
+ """
26
+ Emit an SDK telemetry event.
27
+
28
+ This is an internal function for tracking SDK events.
29
+ Events are queued for processing in a background thread (non-blocking).
30
+
31
+ Args:
32
+ event_name: The name of the event to track
33
+ extra_properties: Additional event properties
34
+
35
+ Returns:
36
+ True if the event was queued, False otherwise
37
+ """
38
+ try:
39
+ device_id = get_or_create_device_id()
40
+ event = AnalyticsEvent(
41
+ event_name=event_name,
42
+ device_id=device_id,
43
+ extra_properties=extra_properties,
44
+ )
45
+ AnalyticsService.instance().enqueue(event)
46
+ return True
47
+ except Exception:
48
+ return False
49
+
50
+
51
+ def emit_integration_event(
52
+ integration: str,
53
+ event_name: str,
54
+ extra_properties: dict[str, Any] | None = None,
55
+ ) -> bool:
56
+ """
57
+ Emit a telemetry event from an integration library.
58
+
59
+ This is exposed via the public API in prefect.analytics for integration
60
+ libraries (e.g., prefect-aws, prefect-gcp) to emit telemetry events.
61
+ Events are automatically namespaced with the integration name.
62
+
63
+ Args:
64
+ integration: The integration name (e.g., "prefect-aws", "prefect-gcp")
65
+ event_name: The event name (e.g., "s3_block_created")
66
+ extra_properties: Additional event properties
67
+
68
+ Returns:
69
+ True if the event was queued, False otherwise
70
+ """
71
+ try:
72
+ # Namespace the event with the integration name
73
+ namespaced_event = f"{integration}:{event_name}"
74
+
75
+ device_id = get_or_create_device_id()
76
+
77
+ # Add integration name to properties
78
+ properties = {"integration": integration}
79
+ if extra_properties:
80
+ properties.update(extra_properties)
81
+
82
+ event = AnalyticsEvent(
83
+ event_name=namespaced_event,
84
+ device_id=device_id,
85
+ extra_properties=properties,
86
+ )
87
+ AnalyticsService.instance().enqueue(event)
88
+ return True
89
+ except Exception:
90
+ return False
@@ -0,0 +1,33 @@
1
+ """
2
+ Telemetry enabled check for SDK analytics.
3
+ """
4
+
5
+ import os
6
+
7
+ from prefect._internal.analytics.ci_detection import is_ci_environment
8
+
9
+
10
+ def is_telemetry_enabled() -> bool:
11
+ """
12
+ Quick non-blocking check of local telemetry settings.
13
+
14
+ Telemetry is disabled if:
15
+ - DO_NOT_TRACK environment variable is set (client-side)
16
+ - Running in a CI environment
17
+
18
+ Note: Server-side analytics check is performed in the background service
19
+ to avoid blocking the main thread.
20
+
21
+ Returns:
22
+ True if local telemetry checks pass, False otherwise
23
+ """
24
+ # Check DO_NOT_TRACK standard (client-side setting)
25
+ do_not_track = os.environ.get("DO_NOT_TRACK", "").lower()
26
+ if do_not_track in ("1", "true", "yes"):
27
+ return False
28
+
29
+ # Check CI environment
30
+ if is_ci_environment():
31
+ return False
32
+
33
+ return True
@@ -0,0 +1,14 @@
1
+ """
2
+ Event type definitions for SDK telemetry.
3
+ """
4
+
5
+ from typing import Literal
6
+
7
+ # Quick Start Funnel events
8
+ SDKEvent = Literal[
9
+ "first_sdk_import",
10
+ "first_flow_defined",
11
+ "first_flow_run",
12
+ "first_deployment_created",
13
+ "first_schedule_created",
14
+ ]
@@ -0,0 +1,177 @@
1
+ """
2
+ Milestone tracking for SDK telemetry.
3
+
4
+ Tracks first-* events to avoid duplicate telemetry.
5
+ Milestones are stored in $PREFECT_HOME/.sdk_telemetry/milestones.json
6
+
7
+ For existing users (detected by presence of Prefect artifacts), all milestones
8
+ are pre-marked as reached to avoid emitting onboarding events on upgrade.
9
+ """
10
+
11
+ import json
12
+ from pathlib import Path
13
+ from typing import Literal
14
+
15
+ from prefect._internal.analytics.emit import _is_interactive_terminal, emit_sdk_event
16
+ from prefect.settings import get_current_settings
17
+
18
+ MilestoneName = Literal[
19
+ "first_sdk_import",
20
+ "first_flow_defined",
21
+ "first_flow_run",
22
+ "first_deployment_created",
23
+ "first_schedule_created",
24
+ ]
25
+
26
+ # All milestone names for pre-marking existing users
27
+ ALL_MILESTONES: list[MilestoneName] = [
28
+ "first_sdk_import",
29
+ "first_flow_defined",
30
+ "first_flow_run",
31
+ "first_deployment_created",
32
+ "first_schedule_created",
33
+ ]
34
+
35
+ # Files/directories that indicate an existing Prefect user
36
+ _EXISTING_USER_INDICATORS = [
37
+ "profiles.toml", # User has configured profiles
38
+ ".prefect.db", # Local server database
39
+ "prefect.db", # Alternative database location
40
+ "storage", # Result storage directory
41
+ ]
42
+
43
+
44
+ def _get_telemetry_dir() -> Path:
45
+ """Get the path to the telemetry directory."""
46
+ settings = get_current_settings()
47
+ return settings.home / ".sdk_telemetry"
48
+
49
+
50
+ def _get_milestones_path() -> Path:
51
+ """Get the path to the milestones file."""
52
+ return _get_telemetry_dir() / "milestones.json"
53
+
54
+
55
+ def _read_milestones() -> dict[str, bool]:
56
+ """Read milestones from disk."""
57
+ milestones_path = _get_milestones_path()
58
+ if milestones_path.exists():
59
+ try:
60
+ return json.loads(milestones_path.read_text())
61
+ except Exception:
62
+ pass
63
+ return {}
64
+
65
+
66
+ def _write_milestones(milestones: dict[str, bool]) -> None:
67
+ """Write milestones to disk."""
68
+ milestones_path = _get_milestones_path()
69
+ try:
70
+ milestones_path.parent.mkdir(parents=True, exist_ok=True)
71
+ milestones_path.write_text(json.dumps(milestones, indent=2))
72
+ except Exception:
73
+ pass
74
+
75
+
76
+ def _is_existing_user() -> bool:
77
+ """
78
+ Check if this is an existing Prefect user.
79
+
80
+ An existing user is detected by the presence of common Prefect artifacts
81
+ in PREFECT_HOME that would have been created before telemetry was added.
82
+
83
+ Returns:
84
+ True if existing user indicators are found
85
+ """
86
+ settings = get_current_settings()
87
+ prefect_home = settings.home
88
+
89
+ if not prefect_home.exists():
90
+ return False
91
+
92
+ for indicator in _EXISTING_USER_INDICATORS:
93
+ if (prefect_home / indicator).exists():
94
+ return True
95
+
96
+ return False
97
+
98
+
99
+ def _mark_existing_user_milestones() -> bool:
100
+ """
101
+ Pre-mark all milestones for existing users.
102
+
103
+ This prevents existing users from emitting onboarding events when they
104
+ upgrade to a version with telemetry.
105
+
106
+ Returns:
107
+ True if milestones were pre-marked (existing user detected)
108
+ """
109
+ telemetry_dir = _get_telemetry_dir()
110
+
111
+ # If telemetry directory already exists, we've already handled this
112
+ if telemetry_dir.exists():
113
+ return False
114
+
115
+ # Check if this is an existing user
116
+ if not _is_existing_user():
117
+ return False
118
+
119
+ # Pre-mark all milestones for existing users
120
+ milestones = {milestone: True for milestone in ALL_MILESTONES}
121
+ _write_milestones(milestones)
122
+ return True
123
+
124
+
125
+ def has_reached_milestone(milestone: MilestoneName) -> bool:
126
+ """
127
+ Check if a milestone has been reached.
128
+
129
+ Args:
130
+ milestone: The milestone name to check
131
+
132
+ Returns:
133
+ True if the milestone has been recorded
134
+ """
135
+ milestones = _read_milestones()
136
+ return milestones.get(milestone, False)
137
+
138
+
139
+ def mark_milestone(milestone: MilestoneName) -> None:
140
+ """
141
+ Mark a milestone as reached.
142
+
143
+ Args:
144
+ milestone: The milestone name to mark
145
+ """
146
+ milestones = _read_milestones()
147
+ milestones[milestone] = True
148
+ _write_milestones(milestones)
149
+
150
+
151
+ def try_mark_milestone(milestone: MilestoneName) -> bool:
152
+ """
153
+ Try to mark a milestone and emit an event if it's new.
154
+
155
+ This is the primary entry point for milestone tracking.
156
+ It checks if the milestone is new, marks it, and emits the event.
157
+
158
+ Events are only emitted in interactive terminals to avoid tracking
159
+ deployed flow runs (e.g., Kubernetes jobs) as new users.
160
+
161
+ Args:
162
+ milestone: The milestone name to mark
163
+
164
+ Returns:
165
+ True if this was the first time reaching the milestone
166
+ """
167
+ # Only emit events in interactive terminals
168
+ # This prevents deployed flow runs from being counted as new users
169
+ if not _is_interactive_terminal():
170
+ return False
171
+
172
+ if has_reached_milestone(milestone):
173
+ return False
174
+
175
+ mark_milestone(milestone)
176
+ emit_sdk_event(milestone)
177
+ return True