prefect-client 3.0.0rc19__py3-none-any.whl → 3.0.1__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 (49) hide show
  1. prefect/__init__.py +0 -3
  2. prefect/_internal/compatibility/migration.py +1 -1
  3. prefect/artifacts.py +1 -1
  4. prefect/blocks/core.py +8 -5
  5. prefect/blocks/notifications.py +10 -10
  6. prefect/blocks/system.py +52 -16
  7. prefect/blocks/webhook.py +3 -1
  8. prefect/client/cloud.py +57 -7
  9. prefect/client/collections.py +1 -1
  10. prefect/client/orchestration.py +68 -7
  11. prefect/client/schemas/objects.py +40 -2
  12. prefect/concurrency/asyncio.py +8 -2
  13. prefect/concurrency/services.py +16 -6
  14. prefect/concurrency/sync.py +4 -1
  15. prefect/context.py +7 -9
  16. prefect/deployments/runner.py +3 -3
  17. prefect/exceptions.py +12 -0
  18. prefect/filesystems.py +5 -3
  19. prefect/flow_engine.py +16 -10
  20. prefect/flows.py +2 -4
  21. prefect/futures.py +2 -1
  22. prefect/locking/__init__.py +0 -0
  23. prefect/locking/memory.py +213 -0
  24. prefect/locking/protocol.py +122 -0
  25. prefect/logging/handlers.py +4 -1
  26. prefect/main.py +8 -6
  27. prefect/records/filesystem.py +4 -2
  28. prefect/records/result_store.py +12 -6
  29. prefect/results.py +768 -363
  30. prefect/settings.py +24 -10
  31. prefect/states.py +82 -27
  32. prefect/task_engine.py +51 -26
  33. prefect/task_worker.py +6 -4
  34. prefect/tasks.py +24 -6
  35. prefect/transactions.py +57 -36
  36. prefect/utilities/annotations.py +4 -3
  37. prefect/utilities/asyncutils.py +1 -1
  38. prefect/utilities/callables.py +1 -3
  39. prefect/utilities/dispatch.py +16 -11
  40. prefect/utilities/schema_tools/hydration.py +13 -0
  41. prefect/variables.py +34 -24
  42. prefect/workers/base.py +78 -18
  43. prefect/workers/process.py +1 -3
  44. {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/METADATA +2 -2
  45. {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/RECORD +48 -46
  46. prefect/manifests.py +0 -21
  47. {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/LICENSE +0 -0
  48. {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/WHEEL +0 -0
  49. {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/top_level.txt +0 -0
prefect/transactions.py CHANGED
@@ -17,17 +17,13 @@ from typing import (
17
17
  from pydantic import Field, PrivateAttr
18
18
  from typing_extensions import Self
19
19
 
20
- from prefect.context import ContextModel, FlowRunContext, TaskRunContext
21
- from prefect.exceptions import MissingContextError
20
+ from prefect.context import ContextModel
21
+ from prefect.exceptions import MissingContextError, SerializationError
22
22
  from prefect.logging.loggers import get_logger, get_run_logger
23
23
  from prefect.records import RecordStore
24
- from prefect.results import (
25
- BaseResult,
26
- ResultFactory,
27
- get_default_result_storage,
28
- )
24
+ from prefect.records.base import TransactionRecord
25
+ from prefect.results import BaseResult, ResultRecord, ResultStore
29
26
  from prefect.utilities.annotations import NotSet
30
- from prefect.utilities.asyncutils import run_coro_as_sync
31
27
  from prefect.utilities.collections import AutoEnum
32
28
  from prefect.utilities.engine import _get_hook_name
33
29
 
@@ -56,7 +52,7 @@ class Transaction(ContextModel):
56
52
  A base model for transaction state.
57
53
  """
58
54
 
59
- store: Optional[RecordStore] = None
55
+ store: Union[RecordStore, ResultStore, None] = None
60
56
  key: Optional[str] = None
61
57
  children: List["Transaction"] = Field(default_factory=list)
62
58
  commit_mode: Optional[CommitMode] = None
@@ -177,10 +173,14 @@ class Transaction(ContextModel):
177
173
  ):
178
174
  self.state = TransactionState.COMMITTED
179
175
 
180
- def read(self) -> Optional[BaseResult]:
176
+ def read(self) -> Union["BaseResult", ResultRecord, None]:
181
177
  if self.store and self.key:
182
178
  record = self.store.read(key=self.key)
183
- if record is not None:
179
+ if isinstance(record, ResultRecord):
180
+ return record
181
+ # for backwards compatibility, if we encounter a transaction record, return the result
182
+ # This happens when the transaction is using a `ResultStore`
183
+ if isinstance(record, TransactionRecord):
184
184
  return record.result
185
185
  return None
186
186
 
@@ -230,7 +230,13 @@ class Transaction(ContextModel):
230
230
  self.run_hook(hook, "commit")
231
231
 
232
232
  if self.store and self.key:
233
- self.store.write(key=self.key, result=self._staged_value)
233
+ if isinstance(self.store, ResultStore):
234
+ if isinstance(self._staged_value, BaseResult):
235
+ self.store.write(self.key, self._staged_value.get(_sync=True))
236
+ else:
237
+ self.store.write(self.key, self._staged_value)
238
+ else:
239
+ self.store.write(self.key, self._staged_value)
234
240
  self.state = TransactionState.COMMITTED
235
241
  if (
236
242
  self.store
@@ -240,6 +246,14 @@ class Transaction(ContextModel):
240
246
  self.logger.debug(f"Releasing lock for transaction {self.key!r}")
241
247
  self.store.release_lock(self.key)
242
248
  return True
249
+ except SerializationError as exc:
250
+ if self.logger:
251
+ self.logger.warning(
252
+ f"Encountered an error while serializing result for transaction {self.key!r}: {exc}"
253
+ " Code execution will continue, but the transaction will not be committed.",
254
+ )
255
+ self.rollback()
256
+ return False
243
257
  except Exception:
244
258
  if self.logger:
245
259
  self.logger.exception(
@@ -251,23 +265,29 @@ class Transaction(ContextModel):
251
265
 
252
266
  def run_hook(self, hook, hook_type: str) -> None:
253
267
  hook_name = _get_hook_name(hook)
254
- self.logger.info(f"Running {hook_type} hook {hook_name!r}")
268
+ # Undocumented way to disable logging for a hook. Subject to change.
269
+ should_log = getattr(hook, "log_on_run", True)
270
+
271
+ if should_log:
272
+ self.logger.info(f"Running {hook_type} hook {hook_name!r}")
255
273
 
256
274
  try:
257
275
  hook(self)
258
276
  except Exception as exc:
259
- self.logger.error(
260
- f"An error was encountered while running {hook_type} hook {hook_name!r}",
261
- )
277
+ if should_log:
278
+ self.logger.error(
279
+ f"An error was encountered while running {hook_type} hook {hook_name!r}",
280
+ )
262
281
  raise exc
263
282
  else:
264
- self.logger.info(
265
- f"{hook_type.capitalize()} hook {hook_name!r} finished running successfully"
266
- )
283
+ if should_log:
284
+ self.logger.info(
285
+ f"{hook_type.capitalize()} hook {hook_name!r} finished running successfully"
286
+ )
267
287
 
268
288
  def stage(
269
289
  self,
270
- value: BaseResult,
290
+ value: Union["BaseResult", Any],
271
291
  on_rollback_hooks: Optional[List] = None,
272
292
  on_commit_hooks: Optional[List] = None,
273
293
  ) -> None:
@@ -325,7 +345,7 @@ def get_transaction() -> Optional[Transaction]:
325
345
  @contextmanager
326
346
  def transaction(
327
347
  key: Optional[str] = None,
328
- store: Optional[RecordStore] = None,
348
+ store: Union[RecordStore, ResultStore, None] = None,
329
349
  commit_mode: Optional[CommitMode] = None,
330
350
  isolation_level: Optional[IsolationLevel] = None,
331
351
  overwrite: bool = False,
@@ -347,23 +367,26 @@ def transaction(
347
367
  """
348
368
  # if there is no key, we won't persist a record
349
369
  if key and not store:
370
+ from prefect.context import FlowRunContext, TaskRunContext
371
+ from prefect.results import ResultStore, get_default_result_storage
372
+
350
373
  flow_run_context = FlowRunContext.get()
351
374
  task_run_context = TaskRunContext.get()
352
- existing_factory = getattr(task_run_context, "result_factory", None) or getattr(
353
- flow_run_context, "result_factory", None
375
+ existing_store = getattr(task_run_context, "result_store", None) or getattr(
376
+ flow_run_context, "result_store", None
354
377
  )
355
378
 
356
- new_factory: ResultFactory
357
- if existing_factory and existing_factory.storage_block_id:
358
- new_factory = existing_factory.model_copy(
379
+ new_store: ResultStore
380
+ if existing_store and existing_store.result_storage_block_id:
381
+ new_store = existing_store.model_copy(
359
382
  update={
360
383
  "persist_result": True,
361
384
  }
362
385
  )
363
386
  else:
364
387
  default_storage = get_default_result_storage(_sync=True)
365
- if existing_factory:
366
- new_factory = existing_factory.model_copy(
388
+ if existing_store:
389
+ new_store = existing_store.model_copy(
367
390
  update={
368
391
  "persist_result": True,
369
392
  "storage_block": default_storage,
@@ -371,16 +394,14 @@ def transaction(
371
394
  }
372
395
  )
373
396
  else:
374
- new_factory = run_coro_as_sync(
375
- ResultFactory.default_factory(
376
- persist_result=True,
377
- result_storage=default_storage,
378
- )
397
+ new_store = ResultStore(
398
+ persist_result=True,
399
+ result_storage=default_storage,
379
400
  )
380
- from prefect.records.result_store import ResultFactoryStore
401
+ from prefect.records.result_store import ResultRecordStore
381
402
 
382
- store = ResultFactoryStore(
383
- result_factory=new_factory,
403
+ store = ResultRecordStore(
404
+ result_store=new_store,
384
405
  )
385
406
 
386
407
  try:
@@ -21,8 +21,8 @@ class BaseAnnotation(
21
21
  def rewrap(self, value: T) -> "BaseAnnotation[T]":
22
22
  return type(self)(value)
23
23
 
24
- def __eq__(self, other: object) -> bool:
25
- if not type(self) == type(other):
24
+ def __eq__(self, other: "BaseAnnotation[T]") -> bool:
25
+ if type(self) is not type(other):
26
26
  return False
27
27
  return self.unwrap() == other.unwrap()
28
28
 
@@ -90,10 +90,11 @@ class quote(BaseAnnotation[T]):
90
90
  class Quote(quote):
91
91
  def __init__(self, expr):
92
92
  warnings.warn(
93
- DeprecationWarning,
94
93
  "Use of `Quote` is deprecated. Use `quote` instead.",
94
+ DeprecationWarning,
95
95
  stacklevel=2,
96
96
  )
97
+ super().__init__(expr)
97
98
 
98
99
 
99
100
  class NotSet:
@@ -403,7 +403,7 @@ def sync_compatible(
403
403
 
404
404
 
405
405
  @asynccontextmanager
406
- async def asyncnullcontext(value=None):
406
+ async def asyncnullcontext(value=None, *args, **kwargs):
407
407
  yield value
408
408
 
409
409
 
@@ -263,9 +263,7 @@ def parameter_docstrings(docstring: Optional[str]) -> Dict[str, str]:
263
263
  if not docstring:
264
264
  return param_docstrings
265
265
 
266
- with disable_logger("griffe.docstrings.google"), disable_logger(
267
- "griffe.agents.nodes"
268
- ):
266
+ with disable_logger("griffe"):
269
267
  parsed = parse(Docstring(docstring), Parser.google)
270
268
  for section in parsed:
271
269
  if section.kind != DocstringSectionKind.parameters:
@@ -162,17 +162,22 @@ def register_type(cls: T) -> T:
162
162
  key = get_dispatch_key(cls)
163
163
  existing_value = registry.get(key)
164
164
  if existing_value is not None and id(existing_value) != id(cls):
165
- # Get line numbers for debugging
166
- file = inspect.getsourcefile(cls)
167
- line_number = inspect.getsourcelines(cls)[1]
168
- existing_file = inspect.getsourcefile(existing_value)
169
- existing_line_number = inspect.getsourcelines(existing_value)[1]
170
- warnings.warn(
171
- f"Type {cls.__name__!r} at {file}:{line_number} has key {key!r} that "
172
- f"matches existing registered type {existing_value.__name__!r} from "
173
- f"{existing_file}:{existing_line_number}. The existing type will be "
174
- "overridden."
175
- )
165
+ try:
166
+ # Get line numbers for debugging
167
+ file = inspect.getsourcefile(cls)
168
+ line_number = inspect.getsourcelines(cls)[1]
169
+ existing_file = inspect.getsourcefile(existing_value)
170
+ existing_line_number = inspect.getsourcelines(existing_value)[1]
171
+ warnings.warn(
172
+ f"Type {cls.__name__!r} at {file}:{line_number} has key {key!r} that "
173
+ f"matches existing registered type {existing_value.__name__!r} from "
174
+ f"{existing_file}:{existing_line_number}. The existing type will be "
175
+ "overridden."
176
+ )
177
+ except OSError:
178
+ # If we can't get the source, another actor is loading this class via eval
179
+ # and we shouldn't update the registry
180
+ return cls
176
181
 
177
182
  # Add to the registry
178
183
  registry[key] = cls
@@ -202,6 +202,11 @@ def json_handler(obj: dict, ctx: HydrationContext):
202
202
  dehydrated_json = _hydrate(obj["value"], ctx)
203
203
  else:
204
204
  dehydrated_json = obj["value"]
205
+
206
+ # If the result is a Placeholder, we should return it as is
207
+ if isinstance(dehydrated_json, Placeholder):
208
+ return dehydrated_json
209
+
205
210
  try:
206
211
  return json.loads(dehydrated_json)
207
212
  except (json.decoder.JSONDecodeError, TypeError) as e:
@@ -224,6 +229,10 @@ def jinja_handler(obj: dict, ctx: HydrationContext):
224
229
  else:
225
230
  dehydrated_jinja = obj["template"]
226
231
 
232
+ # If the result is a Placeholder, we should return it as is
233
+ if isinstance(dehydrated_jinja, Placeholder):
234
+ return dehydrated_jinja
235
+
227
236
  try:
228
237
  validate_user_template(dehydrated_jinja)
229
238
  except (jinja2.exceptions.TemplateSyntaxError, TemplateSecurityError) as exc:
@@ -245,6 +254,10 @@ def workspace_variable_handler(obj: dict, ctx: HydrationContext):
245
254
  else:
246
255
  dehydrated_variable = obj["variable_name"]
247
256
 
257
+ # If the result is a Placeholder, we should return it as is
258
+ if isinstance(dehydrated_variable, Placeholder):
259
+ return dehydrated_variable
260
+
248
261
  if not ctx.render_workspace_variables:
249
262
  return WorkspaceVariable(variable_name=obj["variable_name"])
250
263
 
prefect/variables.py CHANGED
@@ -1,19 +1,18 @@
1
- from typing import List, Optional, Union
1
+ from typing import List, Optional
2
+
3
+ from pydantic import BaseModel, Field
2
4
 
3
5
  from prefect._internal.compatibility.migration import getattr_migration
4
- from prefect.client.schemas.actions import VariableCreate as VariableRequest
5
- from prefect.client.schemas.actions import VariableUpdate as VariableUpdateRequest
6
- from prefect.client.schemas.objects import Variable as VariableResponse
6
+ from prefect.client.schemas.actions import VariableCreate, VariableUpdate
7
7
  from prefect.client.utilities import get_or_create_client
8
8
  from prefect.exceptions import ObjectNotFound
9
- from prefect.types import StrictVariableValue
9
+ from prefect.types import MAX_VARIABLE_NAME_LENGTH, StrictVariableValue
10
10
  from prefect.utilities.asyncutils import sync_compatible
11
11
 
12
12
 
13
- class Variable(VariableRequest):
13
+ class Variable(BaseModel):
14
14
  """
15
- Variables are named, mutable string values, much like environment variables. Variables are scoped to a Prefect server instance or a single workspace in Prefect Cloud.
16
- https://docs.prefect.io/latest/concepts/variables/
15
+ Variables are named, mutable JSON values that can be shared across tasks and flows.
17
16
 
18
17
  Arguments:
19
18
  name: A string identifying the variable.
@@ -21,6 +20,19 @@ class Variable(VariableRequest):
21
20
  tags: An optional list of strings to associate with the variable.
22
21
  """
23
22
 
23
+ name: str = Field(
24
+ default=...,
25
+ description="The name of the variable",
26
+ examples=["my_variable"],
27
+ max_length=MAX_VARIABLE_NAME_LENGTH,
28
+ )
29
+ value: StrictVariableValue = Field(
30
+ default=...,
31
+ description="The value of the variable",
32
+ examples=["my-value"],
33
+ )
34
+ tags: Optional[List[str]] = Field(default=None)
35
+
24
36
  @classmethod
25
37
  @sync_compatible
26
38
  async def set(
@@ -29,22 +41,21 @@ class Variable(VariableRequest):
29
41
  value: StrictVariableValue,
30
42
  tags: Optional[List[str]] = None,
31
43
  overwrite: bool = False,
32
- as_object: bool = False,
33
- ):
44
+ ) -> "Variable":
34
45
  """
35
46
  Sets a new variable. If one exists with the same name, must pass `overwrite=True`
36
47
 
37
- Returns the newly set value. If `as_object=True`, return the full Variable object
48
+ Returns the newly set variable object.
38
49
 
39
50
  Args:
40
51
  - name: The name of the variable to set.
41
52
  - value: The value of the variable to set.
42
53
  - tags: An optional list of strings to associate with the variable.
43
54
  - overwrite: Whether to overwrite the variable if it already exists.
44
- - as_object: Whether to return the full Variable object.
45
55
 
46
56
  Example:
47
57
  Set a new variable and overwrite it if it already exists.
58
+
48
59
  ```
49
60
  from prefect.variables import Variable
50
61
 
@@ -62,14 +73,17 @@ class Variable(VariableRequest):
62
73
  raise ValueError(
63
74
  f"Variable {name!r} already exists. Use `overwrite=True` to update it."
64
75
  )
65
- await client.update_variable(variable=VariableUpdateRequest(**var_dict))
76
+ await client.update_variable(variable=VariableUpdate(**var_dict))
66
77
  variable = await client.read_variable_by_name(name)
78
+ var_dict = {
79
+ "name": variable.name,
80
+ "value": variable.value,
81
+ "tags": variable.tags or [],
82
+ }
67
83
  else:
68
- variable = await client.create_variable(
69
- variable=VariableRequest(**var_dict)
70
- )
84
+ await client.create_variable(variable=VariableCreate(**var_dict))
71
85
 
72
- return variable if as_object else variable.value
86
+ return cls(**var_dict)
73
87
 
74
88
  @classmethod
75
89
  @sync_compatible
@@ -77,19 +91,15 @@ class Variable(VariableRequest):
77
91
  cls,
78
92
  name: str,
79
93
  default: StrictVariableValue = None,
80
- as_object: bool = False,
81
- ) -> Union[StrictVariableValue, VariableResponse]:
94
+ ) -> StrictVariableValue:
82
95
  """
83
96
  Get a variable's value by name.
84
97
 
85
98
  If the variable does not exist, return the default value.
86
99
 
87
- If `as_object=True`, return the full variable object. `default` is ignored in this case.
88
-
89
100
  Args:
90
- - name: The name of the variable to get.
101
+ - name: The name of the variable value to get.
91
102
  - default: The default value to return if the variable does not exist.
92
- - as_object: Whether to return the full variable object.
93
103
 
94
104
  Example:
95
105
  Get a variable's value by name.
@@ -105,7 +115,7 @@ class Variable(VariableRequest):
105
115
  client, _ = get_or_create_client()
106
116
  variable = await client.read_variable_by_name(name)
107
117
 
108
- return variable if as_object else (variable.value if variable else default)
118
+ return variable.value if variable else default
109
119
 
110
120
  @classmethod
111
121
  @sync_compatible
prefect/workers/base.py CHANGED
@@ -19,6 +19,11 @@ from prefect.client.orchestration import PrefectClient, get_client
19
19
  from prefect.client.schemas.actions import WorkPoolCreate, WorkPoolUpdate
20
20
  from prefect.client.schemas.objects import StateType, WorkPool
21
21
  from prefect.client.utilities import inject_client
22
+ from prefect.concurrency.asyncio import (
23
+ AcquireConcurrencySlotTimeoutError,
24
+ ConcurrencySlotAcquisitionError,
25
+ concurrency,
26
+ )
22
27
  from prefect.events import Event, RelatedResource, emit_event
23
28
  from prefect.events.related import object_as_related_resource, tags_as_related_resources
24
29
  from prefect.exceptions import (
@@ -35,7 +40,13 @@ from prefect.settings import (
35
40
  PREFECT_WORKER_QUERY_SECONDS,
36
41
  get_current_settings,
37
42
  )
38
- from prefect.states import Crashed, Pending, exception_to_failed_state
43
+ from prefect.states import (
44
+ AwaitingConcurrencySlot,
45
+ Crashed,
46
+ Pending,
47
+ exception_to_failed_state,
48
+ )
49
+ from prefect.utilities.asyncutils import asyncnullcontext
39
50
  from prefect.utilities.dispatch import get_registry_for_type, register_base_type
40
51
  from prefect.utilities.engine import propose_state
41
52
  from prefect.utilities.services import critical_service_loop
@@ -654,6 +665,7 @@ class BaseWorker(abc.ABC):
654
665
  work_pool = await self._client.read_work_pool(
655
666
  work_pool_name=self._work_pool_name
656
667
  )
668
+
657
669
  except ObjectNotFound:
658
670
  if self._create_pool_if_not_found:
659
671
  wp = WorkPoolCreate(
@@ -747,11 +759,10 @@ class BaseWorker(abc.ABC):
747
759
  for execution by the worker.
748
760
  """
749
761
  submittable_flow_runs = [entry.flow_run for entry in flow_run_response]
750
- submittable_flow_runs.sort(key=lambda run: run.next_scheduled_start_time)
762
+
751
763
  for flow_run in submittable_flow_runs:
752
764
  if flow_run.id in self._submitting_flow_run_ids:
753
765
  continue
754
-
755
766
  try:
756
767
  if self._limiter:
757
768
  self._limiter.acquire_on_behalf_of_nowait(flow_run.id)
@@ -796,8 +807,6 @@ class BaseWorker(abc.ABC):
796
807
  " Please use an agent to execute this flow run."
797
808
  )
798
809
 
799
- #
800
-
801
810
  async def _submit_run(self, flow_run: "FlowRun") -> None:
802
811
  """
803
812
  Submits a given flow run for execution by the worker.
@@ -837,28 +846,59 @@ class BaseWorker(abc.ABC):
837
846
  "not be cancellable."
838
847
  )
839
848
 
840
- run_logger.info(f"Completed submission of flow run '{flow_run.id}'")
849
+ run_logger.info(f"Completed submission of flow run '{flow_run.id}'")
841
850
 
842
- else:
843
- # If the run is not ready to submit, release the concurrency slot
844
- if self._limiter:
845
- self._limiter.release_on_behalf_of(flow_run.id)
851
+ else:
852
+ # If the run is not ready to submit, release the concurrency slot
853
+ if self._limiter:
854
+ self._limiter.release_on_behalf_of(flow_run.id)
846
855
 
847
- self._submitting_flow_run_ids.remove(flow_run.id)
856
+ self._submitting_flow_run_ids.remove(flow_run.id)
848
857
 
849
858
  async def _submit_run_and_capture_errors(
850
859
  self, flow_run: "FlowRun", task_status: Optional[anyio.abc.TaskStatus] = None
851
860
  ) -> Union[BaseWorkerResult, Exception]:
852
861
  run_logger = self.get_flow_run_logger(flow_run)
862
+ deployment = None
863
+
864
+ if flow_run.deployment_id:
865
+ deployment = await self._client.read_deployment(flow_run.deployment_id)
866
+ if deployment and deployment.concurrency_limit:
867
+ limit_name = f"deployment:{deployment.id}"
868
+ concurrency_limit = deployment.concurrency_limit
869
+ concurrency_ctx = concurrency
870
+ else:
871
+ limit_name = None
872
+ concurrency_limit = None
873
+ concurrency_ctx = asyncnullcontext
853
874
 
854
875
  try:
855
- configuration = await self._get_configuration(flow_run)
856
- submitted_event = self._emit_flow_run_submitted_event(configuration)
857
- result = await self.run(
858
- flow_run=flow_run,
859
- task_status=task_status,
860
- configuration=configuration,
876
+ async with concurrency_ctx(
877
+ limit_name, occupy=concurrency_limit, max_retries=0
878
+ ):
879
+ configuration = await self._get_configuration(flow_run, deployment)
880
+ submitted_event = self._emit_flow_run_submitted_event(configuration)
881
+ result = await self.run(
882
+ flow_run=flow_run,
883
+ task_status=task_status,
884
+ configuration=configuration,
885
+ )
886
+ except (
887
+ AcquireConcurrencySlotTimeoutError,
888
+ ConcurrencySlotAcquisitionError,
889
+ ) as exc:
890
+ self._logger.info(
891
+ (
892
+ "Deployment %s has reached its concurrency limit when submitting flow run %s"
893
+ ),
894
+ flow_run.deployment_id,
895
+ flow_run.name,
861
896
  )
897
+ await self._propose_scheduled_state(flow_run)
898
+
899
+ if not task_status._future.done():
900
+ task_status.started(exc)
901
+ return exc
862
902
  except Exception as exc:
863
903
  if not task_status._future.done():
864
904
  # This flow run was being submitted and did not start successfully
@@ -924,8 +964,13 @@ class BaseWorker(abc.ABC):
924
964
  async def _get_configuration(
925
965
  self,
926
966
  flow_run: "FlowRun",
967
+ deployment: Optional["DeploymentResponse"] = None,
927
968
  ) -> BaseJobConfiguration:
928
- deployment = await self._client.read_deployment(flow_run.deployment_id)
969
+ deployment = (
970
+ deployment
971
+ if deployment
972
+ else await self._client.read_deployment(flow_run.deployment_id)
973
+ )
929
974
  flow = await self._client.read_flow(flow_run.flow_id)
930
975
 
931
976
  deployment_vars = deployment.job_variables or {}
@@ -979,6 +1024,21 @@ class BaseWorker(abc.ABC):
979
1024
 
980
1025
  return True
981
1026
 
1027
+ async def _propose_scheduled_state(self, flow_run: "FlowRun") -> None:
1028
+ run_logger = self.get_flow_run_logger(flow_run)
1029
+ try:
1030
+ state = await propose_state(
1031
+ self._client,
1032
+ AwaitingConcurrencySlot(),
1033
+ flow_run_id=flow_run.id,
1034
+ )
1035
+ self._logger.info(f"Flow run {flow_run.id} now has state {state.name}")
1036
+ except Abort:
1037
+ # Flow run already marked as failed
1038
+ pass
1039
+ except Exception:
1040
+ run_logger.exception(f"Failed to update state of flow run '{flow_run.id}'")
1041
+
982
1042
  async def _propose_failed_state(self, flow_run: "FlowRun", exc: Exception) -> None:
983
1043
  run_logger = self.get_flow_run_logger(flow_run)
984
1044
  try:
@@ -144,9 +144,7 @@ class ProcessWorker(BaseWorker):
144
144
  " when first getting started."
145
145
  )
146
146
  _display_name = "Process"
147
- _documentation_url = (
148
- "https://docs.prefect.io/latest/api-ref/prefect/workers/process/"
149
- )
147
+ _documentation_url = "https://docs.prefect.io/latest/get-started/quickstart"
150
148
  _logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/356e6766a91baf20e1d08bbe16e8b5aaef4d8643-48x48.png"
151
149
 
152
150
  async def start(
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prefect-client
3
- Version: 3.0.0rc19
3
+ Version: 3.0.1
4
4
  Summary: Workflow orchestration and management.
5
5
  Home-page: https://www.prefect.io
6
6
  Author: Prefect Technologies, Inc.
7
7
  Author-email: help@prefect.io
8
8
  License: UNKNOWN
9
- Project-URL: Changelog, https://github.com/PrefectHQ/prefect/blob/main/RELEASE-NOTES.md
9
+ Project-URL: Changelog, https://github.com/PrefectHQ/prefect/releases
10
10
  Project-URL: Documentation, https://docs.prefect.io
11
11
  Project-URL: Source, https://github.com/PrefectHQ/prefect
12
12
  Project-URL: Tracker, https://github.com/PrefectHQ/prefect/issues