prefect-client 3.0.0rc18__tar.gz → 3.0.0rc19__tar.gz

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 (212) hide show
  1. {prefect-client-3.0.0rc18/src/prefect_client.egg-info → prefect-client-3.0.0rc19}/PKG-INFO +1 -1
  2. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/requirements-client.txt +2 -3
  3. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/requirements.txt +4 -5
  4. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/concurrency/services.py +14 -0
  5. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/schemas/bases.py +1 -0
  6. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/blocks/core.py +36 -29
  7. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/orchestration.py +97 -2
  8. prefect-client-3.0.0rc19/src/prefect/concurrency/v1/asyncio.py +143 -0
  9. prefect-client-3.0.0rc19/src/prefect/concurrency/v1/context.py +27 -0
  10. prefect-client-3.0.0rc19/src/prefect/concurrency/v1/events.py +61 -0
  11. prefect-client-3.0.0rc19/src/prefect/concurrency/v1/services.py +116 -0
  12. prefect-client-3.0.0rc19/src/prefect/concurrency/v1/sync.py +92 -0
  13. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/context.py +2 -2
  14. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/deployments/flow_runs.py +0 -7
  15. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/deployments/runner.py +11 -0
  16. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/events/clients.py +41 -0
  17. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/events/related.py +72 -73
  18. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/events/utilities.py +2 -0
  19. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/events/worker.py +12 -3
  20. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/flow_engine.py +2 -0
  21. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/flows.py +7 -0
  22. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/records/base.py +74 -18
  23. prefect-client-3.0.0rc19/src/prefect/records/filesystem.py +207 -0
  24. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/records/memory.py +16 -3
  25. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/records/result_store.py +19 -14
  26. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/results.py +11 -0
  27. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/runner/runner.py +7 -4
  28. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/settings.py +0 -8
  29. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/task_engine.py +98 -209
  30. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/task_worker.py +7 -39
  31. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/tasks.py +0 -7
  32. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/transactions.py +67 -19
  33. prefect-client-3.0.0rc19/src/prefect/utilities/__init__.py +0 -0
  34. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/asyncutils.py +3 -3
  35. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/callables.py +1 -3
  36. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/engine.py +1 -4
  37. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19/src/prefect_client.egg-info}/PKG-INFO +1 -1
  38. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect_client.egg-info/SOURCES.txt +7 -0
  39. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect_client.egg-info/requires.txt +2 -3
  40. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/LICENSE +0 -0
  41. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/MANIFEST.in +0 -0
  42. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/README.md +0 -0
  43. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/requirements-dev.txt +0 -0
  44. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/setup.cfg +0 -0
  45. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/setup.py +0 -0
  46. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/.prefectignore +0 -0
  47. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/__init__.py +0 -0
  48. {prefect-client-3.0.0rc18/src/prefect/utilities → prefect-client-3.0.0rc19/src/prefect/_internal}/__init__.py +0 -0
  49. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/_logging.py +0 -0
  50. {prefect-client-3.0.0rc18/src/prefect/events/schemas → prefect-client-3.0.0rc19/src/prefect/_internal/compatibility}/__init__.py +0 -0
  51. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/compatibility/deprecated.py +0 -0
  52. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/compatibility/experimental.py +0 -0
  53. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/compatibility/migration.py +0 -0
  54. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/concurrency/__init__.py +0 -0
  55. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/concurrency/api.py +0 -0
  56. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/concurrency/calls.py +0 -0
  57. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/concurrency/cancellation.py +0 -0
  58. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/concurrency/event_loop.py +0 -0
  59. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/concurrency/inspection.py +0 -0
  60. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/concurrency/primitives.py +0 -0
  61. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/concurrency/threads.py +0 -0
  62. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/concurrency/waiters.py +0 -0
  63. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/integrations.py +0 -0
  64. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/pydantic/__init__.py +0 -0
  65. {prefect-client-3.0.0rc18/src/prefect/events/cli → prefect-client-3.0.0rc19/src/prefect/_internal/pydantic/annotations}/__init__.py +0 -0
  66. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/pydantic/annotations/pendulum.py +0 -0
  67. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/pydantic/schemas.py +0 -0
  68. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/pydantic/v1_schema.py +0 -0
  69. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/pydantic/v2_schema.py +0 -0
  70. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/pydantic/v2_validated_func.py +0 -0
  71. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/pytz.py +0 -0
  72. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/retries.py +0 -0
  73. {prefect-client-3.0.0rc18/src/prefect/concurrency → prefect-client-3.0.0rc19/src/prefect/_internal/schemas}/__init__.py +0 -0
  74. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/schemas/fields.py +0 -0
  75. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/schemas/serializers.py +0 -0
  76. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_internal/schemas/validators.py +0 -0
  77. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/_version.py +0 -0
  78. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/agent.py +0 -0
  79. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/artifacts.py +0 -0
  80. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/automations.py +0 -0
  81. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/blocks/__init__.py +0 -0
  82. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/blocks/abstract.py +0 -0
  83. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/blocks/fields.py +0 -0
  84. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/blocks/notifications.py +0 -0
  85. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/blocks/redis.py +0 -0
  86. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/blocks/system.py +0 -0
  87. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/blocks/webhook.py +0 -0
  88. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/cache_policies.py +0 -0
  89. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/__init__.py +0 -0
  90. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/base.py +0 -0
  91. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/cloud.py +0 -0
  92. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/collections.py +0 -0
  93. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/constants.py +0 -0
  94. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/schemas/__init__.py +0 -0
  95. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/schemas/actions.py +0 -0
  96. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/schemas/filters.py +0 -0
  97. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/schemas/objects.py +0 -0
  98. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/schemas/responses.py +0 -0
  99. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/schemas/schedules.py +0 -0
  100. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/schemas/sorting.py +0 -0
  101. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/subscriptions.py +0 -0
  102. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/types/__init__.py +0 -0
  103. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/types/flexible_schedule_list.py +0 -0
  104. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/client/utilities.py +0 -0
  105. {prefect-client-3.0.0rc18/src/prefect/_internal/schemas → prefect-client-3.0.0rc19/src/prefect/concurrency}/__init__.py +0 -0
  106. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/concurrency/asyncio.py +0 -0
  107. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/concurrency/context.py +0 -0
  108. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/concurrency/events.py +0 -0
  109. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/concurrency/services.py +0 -0
  110. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/concurrency/sync.py +0 -0
  111. {prefect-client-3.0.0rc18/src/prefect/_internal/pydantic/annotations → prefect-client-3.0.0rc19/src/prefect/concurrency/v1}/__init__.py +0 -0
  112. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/deployments/__init__.py +0 -0
  113. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/deployments/base.py +0 -0
  114. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/deployments/deployments.py +0 -0
  115. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/deployments/schedules.py +0 -0
  116. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/deployments/steps/__init__.py +0 -0
  117. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/deployments/steps/core.py +0 -0
  118. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/deployments/steps/pull.py +0 -0
  119. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/deployments/steps/utility.py +0 -0
  120. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/docker/__init__.py +0 -0
  121. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/docker/docker_image.py +0 -0
  122. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/engine.py +0 -0
  123. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/events/__init__.py +0 -0
  124. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/events/actions.py +0 -0
  125. {prefect-client-3.0.0rc18/src/prefect/_internal/compatibility → prefect-client-3.0.0rc19/src/prefect/events/cli}/__init__.py +0 -0
  126. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/events/cli/automations.py +0 -0
  127. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/events/filters.py +0 -0
  128. {prefect-client-3.0.0rc18/src/prefect/_internal → prefect-client-3.0.0rc19/src/prefect/events/schemas}/__init__.py +0 -0
  129. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/events/schemas/automations.py +0 -0
  130. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/events/schemas/deployment_triggers.py +0 -0
  131. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/events/schemas/events.py +0 -0
  132. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/events/schemas/labelling.py +0 -0
  133. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/exceptions.py +0 -0
  134. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/filesystems.py +0 -0
  135. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/flow_runs.py +0 -0
  136. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/futures.py +0 -0
  137. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/infrastructure/__init__.py +0 -0
  138. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/infrastructure/base.py +0 -0
  139. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/infrastructure/provisioners/__init__.py +0 -0
  140. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/infrastructure/provisioners/cloud_run.py +0 -0
  141. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/infrastructure/provisioners/container_instance.py +0 -0
  142. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/infrastructure/provisioners/ecs.py +0 -0
  143. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/infrastructure/provisioners/modal.py +0 -0
  144. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/input/__init__.py +0 -0
  145. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/input/actions.py +0 -0
  146. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/input/run_input.py +0 -0
  147. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/logging/__init__.py +0 -0
  148. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/logging/configuration.py +0 -0
  149. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/logging/filters.py +0 -0
  150. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/logging/formatters.py +0 -0
  151. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/logging/handlers.py +0 -0
  152. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/logging/highlighters.py +0 -0
  153. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/logging/loggers.py +0 -0
  154. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/logging/logging.yml +0 -0
  155. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/main.py +0 -0
  156. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/manifests.py +0 -0
  157. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/plugins.py +0 -0
  158. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/profiles.toml +0 -0
  159. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/py.typed +0 -0
  160. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/records/__init__.py +0 -0
  161. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/runner/__init__.py +0 -0
  162. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/runner/server.py +0 -0
  163. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/runner/storage.py +0 -0
  164. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/runner/submit.py +0 -0
  165. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/runner/utils.py +0 -0
  166. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/runtime/__init__.py +0 -0
  167. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/runtime/deployment.py +0 -0
  168. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/runtime/flow_run.py +0 -0
  169. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/runtime/task_run.py +0 -0
  170. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/serializers.py +0 -0
  171. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/server/api/collections_data/views/aggregate-worker-metadata.json +0 -0
  172. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/server/api/static/prefect-logo-mark-gradient.png +0 -0
  173. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/states.py +0 -0
  174. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/task_runners.py +0 -0
  175. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/task_runs.py +0 -0
  176. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/types/__init__.py +0 -0
  177. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/types/entrypoint.py +0 -0
  178. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/annotations.py +0 -0
  179. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/collections.py +0 -0
  180. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/compat.py +0 -0
  181. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/context.py +0 -0
  182. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/dispatch.py +0 -0
  183. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/dockerutils.py +0 -0
  184. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/filesystem.py +0 -0
  185. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/hashing.py +0 -0
  186. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/importtools.py +0 -0
  187. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/math.py +0 -0
  188. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/names.py +0 -0
  189. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/processutils.py +0 -0
  190. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/pydantic.py +0 -0
  191. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/render_swagger.py +0 -0
  192. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/schema_tools/__init__.py +0 -0
  193. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/schema_tools/hydration.py +0 -0
  194. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/schema_tools/validation.py +0 -0
  195. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/services.py +0 -0
  196. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/slugify.py +0 -0
  197. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/templating.py +0 -0
  198. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/text.py +0 -0
  199. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/timeout.py +0 -0
  200. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/urls.py +0 -0
  201. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/utilities/visualization.py +0 -0
  202. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/variables.py +0 -0
  203. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/workers/__init__.py +0 -0
  204. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/workers/base.py +0 -0
  205. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/workers/block.py +0 -0
  206. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/workers/cloud.py +0 -0
  207. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/workers/process.py +0 -0
  208. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/workers/server.py +0 -0
  209. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect/workers/utilities.py +0 -0
  210. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect_client.egg-info/dependency_links.txt +0 -0
  211. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/src/prefect_client.egg-info/top_level.txt +0 -0
  212. {prefect-client-3.0.0rc18 → prefect-client-3.0.0rc19}/versioneer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prefect-client
3
- Version: 3.0.0rc18
3
+ Version: 3.0.0rc19
4
4
  Summary: Workflow orchestration and management.
5
5
  Home-page: https://www.prefect.io
6
6
  Author: Prefect Technologies, Inc.
@@ -8,11 +8,10 @@ exceptiongroup >= 1.0.0
8
8
  fastapi >= 0.111.0, < 1.0.0
9
9
  fsspec >= 2022.5.0
10
10
  graphviz >= 0.20.1
11
- griffe >= 0.20.0, <0.48.0
11
+ griffe >= 0.49.0, <2.0.0
12
12
  httpcore >=1.0.5, < 2.0.0
13
13
  httpx[http2] >= 0.23, != 0.23.2
14
14
  importlib_metadata >= 4.4; python_version < '3.10'
15
- importlib-resources >= 6.1.3, < 6.2.0
16
15
  jsonpatch >= 1.32, < 2.0
17
16
  jsonschema >= 4.0.0, < 5.0.0
18
17
  orjson >= 3.7, < 4.0
@@ -35,4 +34,4 @@ toml >= 0.10.0
35
34
  typing_extensions >= 4.5.0, < 5.0.0
36
35
  ujson >= 5.8.0, < 6.0.0
37
36
  uvicorn >=0.14.0, !=0.29.0
38
- websockets >= 10.4, < 13.0
37
+ websockets >= 10.4, < 14.0
@@ -1,18 +1,17 @@
1
1
  -r requirements-client.txt
2
2
 
3
- aiosqlite >= 0.17.0
3
+ aiosqlite >= 0.17.0, < 1.0.0
4
4
  alembic >= 1.7.5, < 2.0.0
5
5
  apprise >= 1.1.0, < 2.0.0
6
- asyncpg >= 0.23
6
+ asyncpg >= 0.23, < 1.0.0
7
7
  click >= 8.0, < 8.2
8
8
  cryptography >= 36.0.1
9
9
  dateparser >= 1.1.1, < 2.0.0
10
- docker >= 4.0
10
+ docker >= 4.0, < 8.0
11
11
  graphviz >= 0.20.1
12
- griffe >= 0.20.0
13
12
  jinja2 >= 3.0.0, < 4.0.0
14
13
  jinja2-humanize-extension >= 0.4.0
15
- humanize >= 4.9.0
14
+ humanize >= 4.9.0, < 5.0.0
16
15
  pytz >= 2021.1, < 2025
17
16
  readchar >= 4.0.0, < 5.0.0
18
17
  sqlalchemy[asyncio] >= 2.0, < 3.0.0
@@ -39,6 +39,7 @@ class QueueService(abc.ABC, Generic[T]):
39
39
  daemon=True,
40
40
  name=f"{type(self).__name__}Thread",
41
41
  )
42
+ self._logger = logging.getLogger(f"{type(self).__name__}")
42
43
 
43
44
  def start(self):
44
45
  logger.debug("Starting service %r", self)
@@ -144,11 +145,24 @@ class QueueService(abc.ABC, Generic[T]):
144
145
  self._done_event.set()
145
146
 
146
147
  async def _main_loop(self):
148
+ last_log_time = 0
149
+ log_interval = 4 # log every 4 seconds
150
+
147
151
  while True:
148
152
  item: T = await self._queue_get_thread.submit(
149
153
  create_call(self._queue.get)
150
154
  ).aresult()
151
155
 
156
+ if self._stopped:
157
+ current_time = asyncio.get_event_loop().time()
158
+ queue_size = self._queue.qsize()
159
+
160
+ if current_time - last_log_time >= log_interval and queue_size > 0:
161
+ self._logger.warning(
162
+ f"Still processing items: {queue_size} items remaining..."
163
+ )
164
+ last_log_time = current_time
165
+
152
166
  if item is None:
153
167
  logger.debug("Exiting service %r", self)
154
168
  self._queue.task_done()
@@ -35,6 +35,7 @@ class PrefectBaseModel(BaseModel):
35
35
 
36
36
  model_config = ConfigDict(
37
37
  ser_json_timedelta="float",
38
+ defer_build=True,
38
39
  extra=(
39
40
  "ignore"
40
41
  if os.getenv("PREFECT_TEST_MODE", "0").lower() not in ["true", "1"]
@@ -24,9 +24,7 @@ from typing import (
24
24
  )
25
25
  from uuid import UUID, uuid4
26
26
 
27
- from griffe.dataclasses import Docstring
28
- from griffe.docstrings.dataclasses import DocstringSection, DocstringSectionKind
29
- from griffe.docstrings.parsers import Parser, parse
27
+ from griffe import Docstring, DocstringSection, DocstringSectionKind, Parser, parse
30
28
  from packaging.version import InvalidVersion, Version
31
29
  from pydantic import (
32
30
  BaseModel,
@@ -130,7 +128,9 @@ def _is_subclass(cls, parent_cls) -> bool:
130
128
  Checks if a given class is a subclass of another class. Unlike issubclass,
131
129
  this will not throw an exception if cls is an instance instead of a type.
132
130
  """
133
- return inspect.isclass(cls) and issubclass(cls, parent_cls)
131
+ # For python<=3.11 inspect.isclass() will return True for parametrized types (e.g. list[str])
132
+ # so we need to check for get_origin() to avoid TypeError for issubclass.
133
+ return inspect.isclass(cls) and not get_origin(cls) and issubclass(cls, parent_cls)
134
134
 
135
135
 
136
136
  def _collect_secret_fields(
@@ -138,12 +138,12 @@ def _collect_secret_fields(
138
138
  ) -> None:
139
139
  """
140
140
  Recursively collects all secret fields from a given type and adds them to the
141
- secrets list, supporting nested Union / BaseModel fields. Also, note, this function
142
- mutates the input secrets list, thus does not return anything.
141
+ secrets list, supporting nested Union / Dict / Tuple / List / BaseModel fields.
142
+ Also, note, this function mutates the input secrets list, thus does not return anything.
143
143
  """
144
- if get_origin(type_) is Union:
145
- for union_type in get_args(type_):
146
- _collect_secret_fields(name, union_type, secrets)
144
+ if get_origin(type_) in (Union, dict, list, tuple):
145
+ for nested_type in get_args(type_):
146
+ _collect_secret_fields(name, nested_type, secrets)
147
147
  return
148
148
  elif _is_subclass(type_, BaseModel):
149
149
  for field_name, field in type_.model_fields.items():
@@ -232,21 +232,25 @@ def schema_extra(schema: Dict[str, Any], model: Type["Block"]):
232
232
 
233
233
  # create block schema references
234
234
  refs = schema["block_schema_references"] = {}
235
+
236
+ def collect_block_schema_references(field_name: str, annotation: type) -> None:
237
+ """Walk through the annotation and collect block schemas for any nested blocks."""
238
+ if Block.is_block_class(annotation):
239
+ if isinstance(refs.get(field_name), list):
240
+ refs[field_name].append(annotation._to_block_schema_reference_dict())
241
+ elif isinstance(refs.get(field_name), dict):
242
+ refs[field_name] = [
243
+ refs[field_name],
244
+ annotation._to_block_schema_reference_dict(),
245
+ ]
246
+ else:
247
+ refs[field_name] = annotation._to_block_schema_reference_dict()
248
+ if get_origin(annotation) in (Union, list, tuple, dict):
249
+ for type_ in get_args(annotation):
250
+ collect_block_schema_references(field_name, type_)
251
+
235
252
  for name, field in model.model_fields.items():
236
- if Block.is_block_class(field.annotation):
237
- refs[name] = field.annotation._to_block_schema_reference_dict()
238
- if get_origin(field.annotation) in [Union, list]:
239
- for type_ in get_args(field.annotation):
240
- if Block.is_block_class(type_):
241
- if isinstance(refs.get(name), list):
242
- refs[name].append(type_._to_block_schema_reference_dict())
243
- elif isinstance(refs.get(name), dict):
244
- refs[name] = [
245
- refs[name],
246
- type_._to_block_schema_reference_dict(),
247
- ]
248
- else:
249
- refs[name] = type_._to_block_schema_reference_dict()
253
+ collect_block_schema_references(name, field.annotation)
250
254
 
251
255
 
252
256
  @register_base_type
@@ -1067,13 +1071,16 @@ class Block(BaseModel, ABC):
1067
1071
  "subclass and not on a Block interface class directly."
1068
1072
  )
1069
1073
 
1074
+ async def register_blocks_in_annotation(annotation: type) -> None:
1075
+ """Walk through the annotation and register any nested blocks."""
1076
+ if Block.is_block_class(annotation):
1077
+ await annotation.register_type_and_schema(client=client)
1078
+ elif get_origin(annotation) in (Union, tuple, list, dict):
1079
+ for inner_annotation in get_args(annotation):
1080
+ await register_blocks_in_annotation(inner_annotation)
1081
+
1070
1082
  for field in cls.model_fields.values():
1071
- if Block.is_block_class(field.annotation):
1072
- await field.annotation.register_type_and_schema(client=client)
1073
- if get_origin(field.annotation) is Union:
1074
- for annotation in get_args(field.annotation):
1075
- if Block.is_block_class(annotation):
1076
- await annotation.register_type_and_schema(client=client)
1083
+ await register_blocks_in_annotation(field.annotation)
1077
1084
 
1078
1085
  try:
1079
1086
  block_type = await client.read_block_type_by_slug(
@@ -939,6 +939,57 @@ class PrefectClient:
939
939
  else:
940
940
  raise
941
941
 
942
+ async def increment_v1_concurrency_slots(
943
+ self,
944
+ names: List[str],
945
+ task_run_id: UUID,
946
+ ) -> httpx.Response:
947
+ """
948
+ Increment concurrency limit slots for the specified limits.
949
+
950
+ Args:
951
+ names (List[str]): A list of limit names for which to increment limits.
952
+ task_run_id (UUID): The task run ID incrementing the limits.
953
+ """
954
+ data = {
955
+ "names": names,
956
+ "task_run_id": str(task_run_id),
957
+ }
958
+
959
+ return await self._client.post(
960
+ "/concurrency_limits/increment",
961
+ json=data,
962
+ )
963
+
964
+ async def decrement_v1_concurrency_slots(
965
+ self,
966
+ names: List[str],
967
+ task_run_id: UUID,
968
+ occupancy_seconds: float,
969
+ ) -> httpx.Response:
970
+ """
971
+ Decrement concurrency limit slots for the specified limits.
972
+
973
+ Args:
974
+ names (List[str]): A list of limit names to decrement.
975
+ task_run_id (UUID): The task run ID that incremented the limits.
976
+ occupancy_seconds (float): The duration in seconds that the limits
977
+ were held.
978
+
979
+ Returns:
980
+ httpx.Response: The HTTP response from the server.
981
+ """
982
+ data = {
983
+ "names": names,
984
+ "task_run_id": str(task_run_id),
985
+ "occupancy_seconds": occupancy_seconds,
986
+ }
987
+
988
+ return await self._client.post(
989
+ "/concurrency_limits/decrement",
990
+ json=data,
991
+ )
992
+
942
993
  async def create_work_queue(
943
994
  self,
944
995
  name: str,
@@ -1599,6 +1650,7 @@ class PrefectClient:
1599
1650
  name: str,
1600
1651
  version: Optional[str] = None,
1601
1652
  schedules: Optional[List[DeploymentScheduleCreate]] = None,
1653
+ concurrency_limit: Optional[int] = None,
1602
1654
  parameters: Optional[Dict[str, Any]] = None,
1603
1655
  description: Optional[str] = None,
1604
1656
  work_queue_name: Optional[str] = None,
@@ -1656,6 +1708,7 @@ class PrefectClient:
1656
1708
  parameter_openapi_schema=parameter_openapi_schema,
1657
1709
  paused=paused,
1658
1710
  schedules=schedules or [],
1711
+ concurrency_limit=concurrency_limit,
1659
1712
  pull_steps=pull_steps,
1660
1713
  enforce_parameter_schema=enforce_parameter_schema,
1661
1714
  )
@@ -2612,6 +2665,7 @@ class PrefectClient:
2612
2665
  async def create_work_pool(
2613
2666
  self,
2614
2667
  work_pool: WorkPoolCreate,
2668
+ overwrite: bool = False,
2615
2669
  ) -> WorkPool:
2616
2670
  """
2617
2671
  Creates a work pool with the provided configuration.
@@ -2629,7 +2683,24 @@ class PrefectClient:
2629
2683
  )
2630
2684
  except httpx.HTTPStatusError as e:
2631
2685
  if e.response.status_code == status.HTTP_409_CONFLICT:
2632
- raise prefect.exceptions.ObjectAlreadyExists(http_exc=e) from e
2686
+ if overwrite:
2687
+ existing_work_pool = await self.read_work_pool(
2688
+ work_pool_name=work_pool.name
2689
+ )
2690
+ if existing_work_pool.type != work_pool.type:
2691
+ warnings.warn(
2692
+ "Overwriting work pool type is not supported. Ignoring provided type.",
2693
+ category=UserWarning,
2694
+ )
2695
+ await self.update_work_pool(
2696
+ work_pool_name=work_pool.name,
2697
+ work_pool=WorkPoolUpdate.model_validate(
2698
+ work_pool.model_dump(exclude={"name", "type"})
2699
+ ),
2700
+ )
2701
+ response = await self._client.get(f"/work_pools/{work_pool.name}")
2702
+ else:
2703
+ raise prefect.exceptions.ObjectAlreadyExists(http_exc=e) from e
2633
2704
  else:
2634
2705
  raise
2635
2706
 
@@ -3156,7 +3227,7 @@ class PrefectClient:
3156
3227
  return pydantic.TypeAdapter(List[Automation]).validate_python(response.json())
3157
3228
 
3158
3229
  async def find_automation(
3159
- self, id_or_name: Union[str, UUID], exit_if_not_found: bool = True
3230
+ self, id_or_name: Union[str, UUID]
3160
3231
  ) -> Optional[Automation]:
3161
3232
  if isinstance(id_or_name, str):
3162
3233
  try:
@@ -4096,3 +4167,27 @@ class SyncPrefectClient:
4096
4167
  "occupancy_seconds": occupancy_seconds,
4097
4168
  },
4098
4169
  )
4170
+
4171
+ def decrement_v1_concurrency_slots(
4172
+ self, names: List[str], occupancy_seconds: float, task_run_id: UUID
4173
+ ) -> httpx.Response:
4174
+ """
4175
+ Release the specified concurrency limits.
4176
+
4177
+ Args:
4178
+ names (List[str]): A list of limit names to decrement.
4179
+ occupancy_seconds (float): The duration in seconds that the slots
4180
+ were held.
4181
+ task_run_id (UUID): The task run ID that incremented the limits.
4182
+
4183
+ Returns:
4184
+ httpx.Response: The HTTP response from the server.
4185
+ """
4186
+ return self._client.post(
4187
+ "/concurrency_limits/decrement",
4188
+ json={
4189
+ "names": names,
4190
+ "occupancy_seconds": occupancy_seconds,
4191
+ "task_run_id": str(task_run_id),
4192
+ },
4193
+ )
@@ -0,0 +1,143 @@
1
+ import asyncio
2
+ from contextlib import asynccontextmanager
3
+ from typing import AsyncGenerator, List, Optional, Union, cast
4
+ from uuid import UUID
5
+
6
+ import anyio
7
+ import httpx
8
+ import pendulum
9
+
10
+ from ...client.schemas.responses import MinimalConcurrencyLimitResponse
11
+
12
+ try:
13
+ from pendulum import Interval
14
+ except ImportError:
15
+ # pendulum < 3
16
+ from pendulum.period import Period as Interval # type: ignore
17
+
18
+ from prefect.client.orchestration import get_client
19
+
20
+ from .context import ConcurrencyContext
21
+ from .events import (
22
+ _emit_concurrency_acquisition_events,
23
+ _emit_concurrency_release_events,
24
+ )
25
+ from .services import ConcurrencySlotAcquisitionService
26
+
27
+
28
+ class ConcurrencySlotAcquisitionError(Exception):
29
+ """Raised when an unhandlable occurs while acquiring concurrency slots."""
30
+
31
+
32
+ class AcquireConcurrencySlotTimeoutError(TimeoutError):
33
+ """Raised when acquiring a concurrency slot times out."""
34
+
35
+
36
+ @asynccontextmanager
37
+ async def concurrency(
38
+ names: Union[str, List[str]],
39
+ task_run_id: UUID,
40
+ timeout_seconds: Optional[float] = None,
41
+ ) -> AsyncGenerator[None, None]:
42
+ """A context manager that acquires and releases concurrency slots from the
43
+ given concurrency limits.
44
+
45
+ Args:
46
+ names: The names of the concurrency limits to acquire slots from.
47
+ task_run_id: The name of the task_run_id that is incrementing the slots.
48
+ timeout_seconds: The number of seconds to wait for the slots to be acquired before
49
+ raising a `TimeoutError`. A timeout of `None` will wait indefinitely.
50
+
51
+ Raises:
52
+ TimeoutError: If the slots are not acquired within the given timeout.
53
+
54
+ Example:
55
+ A simple example of using the async `concurrency` context manager:
56
+ ```python
57
+ from prefect.concurrency.v1.asyncio import concurrency
58
+
59
+ async def resource_heavy():
60
+ async with concurrency("test", task_run_id):
61
+ print("Resource heavy task")
62
+
63
+ async def main():
64
+ await resource_heavy()
65
+ ```
66
+ """
67
+ if not names:
68
+ yield
69
+ return
70
+
71
+ names_normalized: List[str] = names if isinstance(names, list) else [names]
72
+
73
+ limits = await _acquire_concurrency_slots(
74
+ names_normalized,
75
+ task_run_id=task_run_id,
76
+ timeout_seconds=timeout_seconds,
77
+ )
78
+ acquisition_time = pendulum.now("UTC")
79
+ emitted_events = _emit_concurrency_acquisition_events(limits, task_run_id)
80
+
81
+ try:
82
+ yield
83
+ finally:
84
+ occupancy_period = cast(Interval, (pendulum.now("UTC") - acquisition_time))
85
+ try:
86
+ await _release_concurrency_slots(
87
+ names_normalized, task_run_id, occupancy_period.total_seconds()
88
+ )
89
+ except anyio.get_cancelled_exc_class():
90
+ # The task was cancelled before it could release the slots. Add the
91
+ # slots to the cleanup list so they can be released when the
92
+ # concurrency context is exited.
93
+ if ctx := ConcurrencyContext.get():
94
+ ctx.cleanup_slots.append(
95
+ (names_normalized, occupancy_period.total_seconds(), task_run_id)
96
+ )
97
+
98
+ _emit_concurrency_release_events(limits, emitted_events, task_run_id)
99
+
100
+
101
+ async def _acquire_concurrency_slots(
102
+ names: List[str],
103
+ task_run_id: UUID,
104
+ timeout_seconds: Optional[float] = None,
105
+ ) -> List[MinimalConcurrencyLimitResponse]:
106
+ service = ConcurrencySlotAcquisitionService.instance(frozenset(names))
107
+ future = service.send((task_run_id, timeout_seconds))
108
+ response_or_exception = await asyncio.wrap_future(future)
109
+
110
+ if isinstance(response_or_exception, Exception):
111
+ if isinstance(response_or_exception, TimeoutError):
112
+ raise AcquireConcurrencySlotTimeoutError(
113
+ f"Attempt to acquire concurrency limits timed out after {timeout_seconds} second(s)"
114
+ ) from response_or_exception
115
+
116
+ raise ConcurrencySlotAcquisitionError(
117
+ f"Unable to acquire concurrency limits {names!r}"
118
+ ) from response_or_exception
119
+
120
+ return _response_to_concurrency_limit_response(response_or_exception)
121
+
122
+
123
+ async def _release_concurrency_slots(
124
+ names: List[str],
125
+ task_run_id: UUID,
126
+ occupancy_seconds: float,
127
+ ) -> List[MinimalConcurrencyLimitResponse]:
128
+ async with get_client() as client:
129
+ response = await client.decrement_v1_concurrency_slots(
130
+ names=names,
131
+ task_run_id=task_run_id,
132
+ occupancy_seconds=occupancy_seconds,
133
+ )
134
+ return _response_to_concurrency_limit_response(response)
135
+
136
+
137
+ def _response_to_concurrency_limit_response(
138
+ response: httpx.Response,
139
+ ) -> List[MinimalConcurrencyLimitResponse]:
140
+ data = response.json() or []
141
+ return [
142
+ MinimalConcurrencyLimitResponse.model_validate(limit) for limit in data if data
143
+ ]
@@ -0,0 +1,27 @@
1
+ from contextvars import ContextVar
2
+ from typing import List, Tuple
3
+ from uuid import UUID
4
+
5
+ from prefect.client.orchestration import get_client
6
+ from prefect.context import ContextModel, Field
7
+
8
+
9
+ class ConcurrencyContext(ContextModel):
10
+ __var__: ContextVar = ContextVar("concurrency_v1")
11
+
12
+ # Track the limits that have been acquired but were not able to be released
13
+ # due to cancellation or some other error. These limits are released when
14
+ # the context manager exits.
15
+ cleanup_slots: List[Tuple[List[str], float, UUID]] = Field(default_factory=list)
16
+
17
+ def __exit__(self, *exc_info):
18
+ if self.cleanup_slots:
19
+ with get_client(sync_client=True) as client:
20
+ for names, occupancy_seconds, task_run_id in self.cleanup_slots:
21
+ client.decrement_v1_concurrency_slots(
22
+ names=names,
23
+ occupancy_seconds=occupancy_seconds,
24
+ task_run_id=task_run_id,
25
+ )
26
+
27
+ return super().__exit__(*exc_info)
@@ -0,0 +1,61 @@
1
+ from typing import Dict, List, Literal, Optional, Union
2
+ from uuid import UUID
3
+
4
+ from prefect.client.schemas.responses import MinimalConcurrencyLimitResponse
5
+ from prefect.events import Event, RelatedResource, emit_event
6
+
7
+
8
+ def _emit_concurrency_event(
9
+ phase: Union[Literal["acquired"], Literal["released"]],
10
+ primary_limit: MinimalConcurrencyLimitResponse,
11
+ related_limits: List[MinimalConcurrencyLimitResponse],
12
+ task_run_id: UUID,
13
+ follows: Union[Event, None] = None,
14
+ ) -> Union[Event, None]:
15
+ resource: Dict[str, str] = {
16
+ "prefect.resource.id": f"prefect.concurrency-limit.v1.{primary_limit.id}",
17
+ "prefect.resource.name": primary_limit.name,
18
+ "limit": str(primary_limit.limit),
19
+ "task_run_id": str(task_run_id),
20
+ }
21
+
22
+ related = [
23
+ RelatedResource.model_validate(
24
+ {
25
+ "prefect.resource.id": f"prefect.concurrency-limit.v1.{limit.id}",
26
+ "prefect.resource.role": "concurrency-limit",
27
+ }
28
+ )
29
+ for limit in related_limits
30
+ if limit.id != primary_limit.id
31
+ ]
32
+
33
+ return emit_event(
34
+ f"prefect.concurrency-limit.v1.{phase}",
35
+ resource=resource,
36
+ related=related,
37
+ follows=follows,
38
+ )
39
+
40
+
41
+ def _emit_concurrency_acquisition_events(
42
+ limits: List[MinimalConcurrencyLimitResponse],
43
+ task_run_id: UUID,
44
+ ) -> Dict[UUID, Optional[Event]]:
45
+ events = {}
46
+ for limit in limits:
47
+ event = _emit_concurrency_event("acquired", limit, limits, task_run_id)
48
+ events[limit.id] = event
49
+
50
+ return events
51
+
52
+
53
+ def _emit_concurrency_release_events(
54
+ limits: List[MinimalConcurrencyLimitResponse],
55
+ events: Dict[UUID, Optional[Event]],
56
+ task_run_id: UUID,
57
+ ) -> None:
58
+ for limit in limits:
59
+ _emit_concurrency_event(
60
+ "released", limit, limits, task_run_id, events[limit.id]
61
+ )
@@ -0,0 +1,116 @@
1
+ import asyncio
2
+ import concurrent.futures
3
+ from contextlib import asynccontextmanager
4
+ from json import JSONDecodeError
5
+ from typing import (
6
+ TYPE_CHECKING,
7
+ AsyncGenerator,
8
+ FrozenSet,
9
+ Optional,
10
+ Tuple,
11
+ )
12
+ from uuid import UUID
13
+
14
+ import httpx
15
+ from starlette import status
16
+
17
+ from prefect._internal.concurrency import logger
18
+ from prefect._internal.concurrency.services import QueueService
19
+ from prefect.client.orchestration import get_client
20
+ from prefect.utilities.timeout import timeout_async
21
+
22
+ if TYPE_CHECKING:
23
+ from prefect.client.orchestration import PrefectClient
24
+
25
+
26
+ class ConcurrencySlotAcquisitionServiceError(Exception):
27
+ """Raised when an error occurs while acquiring concurrency slots."""
28
+
29
+
30
+ class ConcurrencySlotAcquisitionService(QueueService):
31
+ def __init__(self, concurrency_limit_names: FrozenSet[str]):
32
+ super().__init__(concurrency_limit_names)
33
+ self._client: "PrefectClient"
34
+ self.concurrency_limit_names = sorted(list(concurrency_limit_names))
35
+
36
+ @asynccontextmanager
37
+ async def _lifespan(self) -> AsyncGenerator[None, None]:
38
+ async with get_client() as client:
39
+ self._client = client
40
+ yield
41
+
42
+ async def _handle(
43
+ self,
44
+ item: Tuple[
45
+ UUID,
46
+ concurrent.futures.Future,
47
+ Optional[float],
48
+ ],
49
+ ) -> None:
50
+ task_run_id, future, timeout_seconds = item
51
+ try:
52
+ response = await self.acquire_slots(task_run_id, timeout_seconds)
53
+ except Exception as exc:
54
+ # If the request to the increment endpoint fails in a non-standard
55
+ # way, we need to set the future's result so that the caller can
56
+ # handle the exception and then re-raise.
57
+ future.set_result(exc)
58
+ raise exc
59
+ else:
60
+ future.set_result(response)
61
+
62
+ async def acquire_slots(
63
+ self,
64
+ task_run_id: UUID,
65
+ timeout_seconds: Optional[float] = None,
66
+ ) -> httpx.Response:
67
+ with timeout_async(seconds=timeout_seconds):
68
+ while True:
69
+ try:
70
+ response = await self._client.increment_v1_concurrency_slots(
71
+ task_run_id=task_run_id,
72
+ names=self.concurrency_limit_names,
73
+ )
74
+ except Exception as exc:
75
+ if (
76
+ isinstance(exc, httpx.HTTPStatusError)
77
+ and exc.response.status_code == status.HTTP_423_LOCKED
78
+ ):
79
+ retry_after = exc.response.headers.get("Retry-After")
80
+ if retry_after:
81
+ retry_after = float(retry_after)
82
+ await asyncio.sleep(retry_after)
83
+ else:
84
+ # We received a 423 but no Retry-After header. This
85
+ # should indicate that the server told us to abort
86
+ # because the concurrency limit is set to 0, i.e.
87
+ # effectively disabled.
88
+ try:
89
+ reason = exc.response.json()["detail"]
90
+ except (JSONDecodeError, KeyError):
91
+ logger.error(
92
+ "Failed to parse response from concurrency limit 423 Locked response: %s",
93
+ exc.response.content,
94
+ )
95
+ reason = "Concurrency limit is locked (server did not specify the reason)"
96
+ raise ConcurrencySlotAcquisitionServiceError(
97
+ reason
98
+ ) from exc
99
+
100
+ else:
101
+ raise exc # type: ignore
102
+ else:
103
+ return response
104
+
105
+ def send(self, item: Tuple[UUID, Optional[float]]) -> concurrent.futures.Future:
106
+ with self._lock:
107
+ if self._stopped:
108
+ raise RuntimeError("Cannot put items in a stopped service instance.")
109
+
110
+ logger.debug("Service %r enqueuing item %r", self, item)
111
+ future: concurrent.futures.Future = concurrent.futures.Future()
112
+
113
+ task_run_id, timeout_seconds = item
114
+ self._queue.put_nowait((task_run_id, future, timeout_seconds))
115
+
116
+ return future