prefect-client 3.0.0rc19__py3-none-any.whl → 3.0.0rc20__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
prefect/states.py CHANGED
@@ -18,12 +18,13 @@ from prefect.exceptions import (
18
18
  CancelledRun,
19
19
  CrashedRun,
20
20
  FailedRun,
21
+ MissingContextError,
21
22
  MissingResult,
22
23
  PausedRun,
23
24
  TerminationSignal,
24
25
  UnfinishedRun,
25
26
  )
26
- from prefect.logging.loggers import get_logger
27
+ from prefect.logging.loggers import get_logger, get_run_logger
27
28
  from prefect.results import BaseResult, R, ResultFactory
28
29
  from prefect.settings import PREFECT_ASYNC_FETCH_STATE_RESULT
29
30
  from prefect.utilities.annotations import BaseAnnotation
@@ -218,11 +219,17 @@ async def exception_to_crashed_state(
218
219
  async def exception_to_failed_state(
219
220
  exc: Optional[BaseException] = None,
220
221
  result_factory: Optional[ResultFactory] = None,
222
+ write_result: bool = False,
221
223
  **kwargs,
222
224
  ) -> State:
223
225
  """
224
226
  Convenience function for creating `Failed` states from exceptions
225
227
  """
228
+ try:
229
+ local_logger = get_run_logger()
230
+ except MissingContextError:
231
+ local_logger = logger
232
+
226
233
  if not exc:
227
234
  _, exc, _ = sys.exc_info()
228
235
  if exc is None:
@@ -234,6 +241,14 @@ async def exception_to_failed_state(
234
241
 
235
242
  if result_factory:
236
243
  data = await result_factory.create_result(exc)
244
+ if write_result:
245
+ try:
246
+ await data.write()
247
+ except Exception as exc:
248
+ local_logger.warning(
249
+ "Failed to write result: %s Execution will continue, but the result has not been written",
250
+ exc,
251
+ )
237
252
  else:
238
253
  # Attach the exception for local usage, will not be available when retrieved
239
254
  # from the API
@@ -258,7 +273,7 @@ async def return_value_to_state(
258
273
  result_factory: ResultFactory,
259
274
  key: Optional[str] = None,
260
275
  expiration: Optional[datetime.datetime] = None,
261
- defer_persistence: bool = False,
276
+ write_result: bool = False,
262
277
  ) -> State[R]:
263
278
  """
264
279
  Given a return value from a user's function, create a `State` the run should
@@ -280,6 +295,10 @@ async def return_value_to_state(
280
295
  Callers should resolve all futures into states before passing return values to this
281
296
  function.
282
297
  """
298
+ try:
299
+ local_logger = get_run_logger()
300
+ except MissingContextError:
301
+ local_logger = logger
283
302
 
284
303
  if (
285
304
  isinstance(retval, State)
@@ -291,13 +310,20 @@ async def return_value_to_state(
291
310
  # Unless the user has already constructed a result explicitly, use the factory
292
311
  # to update the data to the correct type
293
312
  if not isinstance(state.data, BaseResult):
294
- state.data = await result_factory.create_result(
313
+ result = await result_factory.create_result(
295
314
  state.data,
296
315
  key=key,
297
316
  expiration=expiration,
298
- defer_persistence=defer_persistence,
299
317
  )
300
-
318
+ if write_result:
319
+ try:
320
+ await result.write()
321
+ except Exception as exc:
322
+ local_logger.warning(
323
+ "Encountered an error while persisting result: %s Execution will continue, but the result has not been persisted",
324
+ exc,
325
+ )
326
+ state.data = result
301
327
  return state
302
328
 
303
329
  # Determine a new state from the aggregate of contained states
@@ -333,15 +359,23 @@ async def return_value_to_state(
333
359
  # TODO: We may actually want to set the data to a `StateGroup` object and just
334
360
  # allow it to be unpacked into a tuple and such so users can interact with
335
361
  # it
362
+ result = await result_factory.create_result(
363
+ retval,
364
+ key=key,
365
+ expiration=expiration,
366
+ )
367
+ if write_result:
368
+ try:
369
+ await result.write()
370
+ except Exception as exc:
371
+ local_logger.warning(
372
+ "Encountered an error while persisting result: %s Execution will continue, but the result has not been persisted",
373
+ exc,
374
+ )
336
375
  return State(
337
376
  type=new_state_type,
338
377
  message=message,
339
- data=await result_factory.create_result(
340
- retval,
341
- key=key,
342
- expiration=expiration,
343
- defer_persistence=defer_persistence,
344
- ),
378
+ data=result,
345
379
  )
346
380
 
347
381
  # Generators aren't portable, implicitly convert them to a list.
@@ -354,14 +388,20 @@ async def return_value_to_state(
354
388
  if isinstance(data, BaseResult):
355
389
  return Completed(data=data)
356
390
  else:
357
- return Completed(
358
- data=await result_factory.create_result(
359
- data,
360
- key=key,
361
- expiration=expiration,
362
- defer_persistence=defer_persistence,
363
- )
391
+ result = await result_factory.create_result(
392
+ data,
393
+ key=key,
394
+ expiration=expiration,
364
395
  )
396
+ if write_result:
397
+ try:
398
+ await result.write()
399
+ except Exception as exc:
400
+ local_logger.warning(
401
+ "Encountered an error while persisting result: %s Execution will continue, but the result has not been persisted",
402
+ exc,
403
+ )
404
+ return Completed(data=result)
365
405
 
366
406
 
367
407
  @sync_compatible
@@ -684,6 +724,21 @@ def AwaitingRetry(
684
724
  )
685
725
 
686
726
 
727
+ def AwaitingConcurrencySlot(
728
+ cls: Type[State[R]] = State,
729
+ scheduled_time: Optional[datetime.datetime] = None,
730
+ **kwargs: Any,
731
+ ) -> State[R]:
732
+ """Convenience function for creating `AwaitingConcurrencySlot` states.
733
+
734
+ Returns:
735
+ State: a AwaitingConcurrencySlot state
736
+ """
737
+ return Scheduled(
738
+ cls=cls, scheduled_time=scheduled_time, name="AwaitingConcurrencySlot", **kwargs
739
+ )
740
+
741
+
687
742
  def Retrying(cls: Type[State[R]] = State, **kwargs: Any) -> State[R]:
688
743
  """Convenience function for creating `Retrying` states.
689
744
 
prefect/task_engine.py CHANGED
@@ -5,6 +5,7 @@ import time
5
5
  from asyncio import CancelledError
6
6
  from contextlib import ExitStack, asynccontextmanager, contextmanager
7
7
  from dataclasses import dataclass, field
8
+ from functools import partial
8
9
  from textwrap import dedent
9
10
  from typing import (
10
11
  Any,
@@ -464,13 +465,16 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
464
465
  result_factory=result_factory,
465
466
  key=transaction.key,
466
467
  expiration=expiration,
467
- # defer persistence to transaction commit
468
- defer_persistence=True,
469
468
  )
470
469
  )
470
+
471
+ # Avoid logging when running this rollback hook since it is not user-defined
472
+ handle_rollback = partial(self.handle_rollback)
473
+ handle_rollback.log_on_run = False
474
+
471
475
  transaction.stage(
472
476
  terminal_state.data,
473
- on_rollback_hooks=[self.handle_rollback] + self.task.on_rollback_hooks,
477
+ on_rollback_hooks=[handle_rollback] + self.task.on_rollback_hooks,
474
478
  on_commit_hooks=self.task.on_commit_hooks,
475
479
  )
476
480
  if transaction.is_committed():
@@ -536,6 +540,7 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
536
540
  exc,
537
541
  message="Task run encountered an exception",
538
542
  result_factory=getattr(context, "result_factory", None),
543
+ write_result=True,
539
544
  )
540
545
  )
541
546
  self.record_terminal_state_timing(state)
@@ -705,17 +710,22 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
705
710
 
706
711
  @contextmanager
707
712
  def transaction_context(self) -> Generator[Transaction, None, None]:
708
- result_factory = getattr(TaskRunContext.get(), "result_factory", None)
709
-
710
713
  # refresh cache setting is now repurposes as overwrite transaction record
711
714
  overwrite = (
712
715
  self.task.refresh_cache
713
716
  if self.task.refresh_cache is not None
714
717
  else PREFECT_TASKS_REFRESH_CACHE.value()
715
718
  )
719
+
720
+ result_factory = getattr(TaskRunContext.get(), "result_factory", None)
721
+ if result_factory and result_factory.persist_result:
722
+ store = ResultFactoryStore(result_factory=result_factory)
723
+ else:
724
+ store = None
725
+
716
726
  with transaction(
717
727
  key=self.compute_transaction_key(),
718
- store=ResultFactoryStore(result_factory=result_factory),
728
+ store=store,
719
729
  overwrite=overwrite,
720
730
  logger=self.logger,
721
731
  ) as txn:
@@ -964,12 +974,15 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
964
974
  result_factory=result_factory,
965
975
  key=transaction.key,
966
976
  expiration=expiration,
967
- # defer persistence to transaction commit
968
- defer_persistence=True,
969
977
  )
978
+
979
+ # Avoid logging when running this rollback hook since it is not user-defined
980
+ handle_rollback = partial(self.handle_rollback)
981
+ handle_rollback.log_on_run = False
982
+
970
983
  transaction.stage(
971
984
  terminal_state.data,
972
- on_rollback_hooks=[self.handle_rollback] + self.task.on_rollback_hooks,
985
+ on_rollback_hooks=[handle_rollback] + self.task.on_rollback_hooks,
973
986
  on_commit_hooks=self.task.on_commit_hooks,
974
987
  )
975
988
  if transaction.is_committed():
@@ -1199,17 +1212,21 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1199
1212
 
1200
1213
  @asynccontextmanager
1201
1214
  async def transaction_context(self) -> AsyncGenerator[Transaction, None]:
1202
- result_factory = getattr(TaskRunContext.get(), "result_factory", None)
1203
-
1204
1215
  # refresh cache setting is now repurposes as overwrite transaction record
1205
1216
  overwrite = (
1206
1217
  self.task.refresh_cache
1207
1218
  if self.task.refresh_cache is not None
1208
1219
  else PREFECT_TASKS_REFRESH_CACHE.value()
1209
1220
  )
1221
+ result_factory = getattr(TaskRunContext.get(), "result_factory", None)
1222
+ if result_factory and result_factory.persist_result:
1223
+ store = ResultFactoryStore(result_factory=result_factory)
1224
+ else:
1225
+ store = None
1226
+
1210
1227
  with transaction(
1211
1228
  key=self.compute_transaction_key(),
1212
- store=ResultFactoryStore(result_factory=result_factory),
1229
+ store=store,
1213
1230
  overwrite=overwrite,
1214
1231
  logger=self.logger,
1215
1232
  ) as txn:
prefect/transactions.py CHANGED
@@ -18,7 +18,7 @@ from pydantic import Field, PrivateAttr
18
18
  from typing_extensions import Self
19
19
 
20
20
  from prefect.context import ContextModel, FlowRunContext, TaskRunContext
21
- from prefect.exceptions import MissingContextError
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
24
  from prefect.results import (
@@ -240,6 +240,14 @@ class Transaction(ContextModel):
240
240
  self.logger.debug(f"Releasing lock for transaction {self.key!r}")
241
241
  self.store.release_lock(self.key)
242
242
  return True
243
+ except SerializationError as exc:
244
+ if self.logger:
245
+ self.logger.warning(
246
+ f"Encountered an error while serializing result for transaction {self.key!r}: {exc}"
247
+ " Code execution will continue, but the transaction will not be committed.",
248
+ )
249
+ self.rollback()
250
+ return False
243
251
  except Exception:
244
252
  if self.logger:
245
253
  self.logger.exception(
@@ -251,19 +259,25 @@ class Transaction(ContextModel):
251
259
 
252
260
  def run_hook(self, hook, hook_type: str) -> None:
253
261
  hook_name = _get_hook_name(hook)
254
- self.logger.info(f"Running {hook_type} hook {hook_name!r}")
262
+ # Undocumented way to disable logging for a hook. Subject to change.
263
+ should_log = getattr(hook, "log_on_run", True)
264
+
265
+ if should_log:
266
+ self.logger.info(f"Running {hook_type} hook {hook_name!r}")
255
267
 
256
268
  try:
257
269
  hook(self)
258
270
  except Exception as exc:
259
- self.logger.error(
260
- f"An error was encountered while running {hook_type} hook {hook_name!r}",
261
- )
271
+ if should_log:
272
+ self.logger.error(
273
+ f"An error was encountered while running {hook_type} hook {hook_name!r}",
274
+ )
262
275
  raise exc
263
276
  else:
264
- self.logger.info(
265
- f"{hook_type.capitalize()} hook {hook_name!r} finished running successfully"
266
- )
277
+ if should_log:
278
+ self.logger.info(
279
+ f"{hook_type.capitalize()} hook {hook_name!r} finished running successfully"
280
+ )
267
281
 
268
282
  def stage(
269
283
  self,
@@ -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
 
@@ -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/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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prefect-client
3
- Version: 3.0.0rc19
3
+ Version: 3.0.0rc20
4
4
  Summary: Workflow orchestration and management.
5
5
  Home-page: https://www.prefect.io
6
6
  Author: Prefect Technologies, Inc.