prefect-client 3.0.0rc16__py3-none-any.whl → 3.0.0rc18__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/blocks/core.py CHANGED
@@ -1046,7 +1046,7 @@ class Block(BaseModel, ABC):
1046
1046
  @classmethod
1047
1047
  @sync_compatible
1048
1048
  @inject_client
1049
- async def register_type_and_schema(cls, client: "PrefectClient" = None):
1049
+ async def register_type_and_schema(cls, client: Optional["PrefectClient"] = None):
1050
1050
  """
1051
1051
  Makes block available for configuration with current Prefect API.
1052
1052
  Recursively registers all nested blocks. Registration is idempotent.
prefect/cache_policies.py CHANGED
@@ -143,8 +143,8 @@ class TaskSource(CachePolicy):
143
143
  def compute_key(
144
144
  self,
145
145
  task_ctx: TaskRunContext,
146
- inputs: Dict[str, Any],
147
- flow_parameters: Dict[str, Any],
146
+ inputs: Optional[Dict[str, Any]],
147
+ flow_parameters: Optional[Dict[str, Any]],
148
148
  **kwargs,
149
149
  ) -> Optional[str]:
150
150
  if not task_ctx:
@@ -153,6 +153,11 @@ class TaskSource(CachePolicy):
153
153
  lines = inspect.getsource(task_ctx.task)
154
154
  except TypeError:
155
155
  lines = inspect.getsource(task_ctx.task.fn.__class__)
156
+ except OSError as exc:
157
+ if "could not get source code" in str(exc):
158
+ lines = task_ctx.task.fn.__code__.co_code
159
+ else:
160
+ raise
156
161
 
157
162
  return hash_objects(lines)
158
163
 
@@ -157,6 +157,10 @@ class DeploymentCreate(ActionBaseModel):
157
157
  default_factory=list,
158
158
  description="A list of schedules for the deployment.",
159
159
  )
160
+ concurrency_limit: Optional[int] = Field(
161
+ default=None,
162
+ description="The concurrency limit for the deployment.",
163
+ )
160
164
  enforce_parameter_schema: Optional[bool] = Field(
161
165
  default=None,
162
166
  description=(
@@ -229,6 +233,10 @@ class DeploymentUpdate(ActionBaseModel):
229
233
  default=None,
230
234
  description="A list of schedules for the deployment.",
231
235
  )
236
+ concurrency_limit: Optional[int] = Field(
237
+ default=None,
238
+ description="The concurrency limit for the deployment.",
239
+ )
232
240
  tags: List[str] = Field(default_factory=list)
233
241
  work_queue_name: Optional[str] = Field(None)
234
242
  work_pool_name: Optional[str] = Field(
@@ -607,11 +615,11 @@ class WorkQueueCreate(ActionBaseModel):
607
615
  default=False,
608
616
  description="Whether the work queue is paused.",
609
617
  )
610
- concurrency_limit: Optional[int] = Field(
618
+ concurrency_limit: Optional[NonNegativeInteger] = Field(
611
619
  default=None,
612
620
  description="A concurrency limit for the work queue.",
613
621
  )
614
- priority: Optional[int] = Field(
622
+ priority: Optional[PositiveInteger] = Field(
615
623
  default=None,
616
624
  description=(
617
625
  "The queue's priority. Lower values are higher priority (1 is the highest)."
@@ -635,8 +643,10 @@ class WorkQueueUpdate(ActionBaseModel):
635
643
  is_paused: bool = Field(
636
644
  default=False, description="Whether or not the work queue is paused."
637
645
  )
638
- concurrency_limit: Optional[int] = Field(None)
639
- priority: Optional[int] = Field(None)
646
+ concurrency_limit: Optional[NonNegativeInteger] = Field(None)
647
+ priority: Optional[PositiveInteger] = Field(
648
+ None, description="The queue's priority."
649
+ )
640
650
  last_polled: Optional[DateTime] = Field(None)
641
651
 
642
652
  # DEPRECATED
@@ -505,6 +505,23 @@ class DeploymentFilterTags(PrefectBaseModel, OperatorMixin):
505
505
  )
506
506
 
507
507
 
508
+ class DeploymentFilterConcurrencyLimit(PrefectBaseModel):
509
+ """Filter by `Deployment.concurrency_limit`."""
510
+
511
+ ge_: Optional[int] = Field(
512
+ default=None,
513
+ description="Only include deployments with a concurrency limit greater than or equal to this value",
514
+ )
515
+ le_: Optional[int] = Field(
516
+ default=None,
517
+ description="Only include deployments with a concurrency limit less than or equal to this value",
518
+ )
519
+ is_null_: Optional[bool] = Field(
520
+ default=None,
521
+ description="If true, only include deployments without a concurrency limit",
522
+ )
523
+
524
+
508
525
  class DeploymentFilter(PrefectBaseModel, OperatorMixin):
509
526
  """Filter for deployments. Only deployments matching all criteria will be returned."""
510
527
 
@@ -520,6 +537,9 @@ class DeploymentFilter(PrefectBaseModel, OperatorMixin):
520
537
  work_queue_name: Optional[DeploymentFilterWorkQueueName] = Field(
521
538
  default=None, description="Filter criteria for `Deployment.work_queue_name`"
522
539
  )
540
+ concurrency_limit: Optional[DeploymentFilterConcurrencyLimit] = Field(
541
+ default=None, description="Filter criteria for `Deployment.concurrency_limit`"
542
+ )
523
543
 
524
544
 
525
545
  class LogFilterName(PrefectBaseModel):
@@ -996,6 +996,9 @@ class Deployment(ObjectBaseModel):
996
996
  paused: bool = Field(
997
997
  default=False, description="Whether or not the deployment is paused."
998
998
  )
999
+ concurrency_limit: Optional[int] = Field(
1000
+ default=None, description="The concurrency limit for the deployment."
1001
+ )
999
1002
  schedules: List[DeploymentSchedule] = Field(
1000
1003
  default_factory=list, description="A list of schedules for the deployment."
1001
1004
  )
@@ -313,6 +313,9 @@ class DeploymentResponse(ObjectBaseModel):
313
313
  flow_id: UUID = Field(
314
314
  default=..., description="The flow id associated with the deployment."
315
315
  )
316
+ concurrency_limit: Optional[int] = Field(
317
+ default=None, description="The concurrency limit for the deployment."
318
+ )
316
319
  paused: bool = Field(
317
320
  default=False, description="Whether or not the deployment is paused."
318
321
  )
@@ -59,6 +59,8 @@ class DeploymentSort(AutoEnum):
59
59
  UPDATED_DESC = AutoEnum.auto()
60
60
  NAME_ASC = AutoEnum.auto()
61
61
  NAME_DESC = AutoEnum.auto()
62
+ CONCURRENCY_LIMIT_ASC = AutoEnum.auto()
63
+ CONCURRENCY_LIMIT_DESC = AutoEnum.auto()
62
64
 
63
65
 
64
66
  class ArtifactSort(AutoEnum):
prefect/flows.py CHANGED
@@ -611,12 +611,17 @@ class Flow(Generic[P, R]):
611
611
  # do not serialize the bound self object
612
612
  if self.ismethod and value is self.fn.__prefect_self__:
613
613
  continue
614
+ if isinstance(value, (PrefectFuture, State)):
615
+ # Don't call jsonable_encoder() on a PrefectFuture or State to
616
+ # avoid triggering a __getitem__ call
617
+ serialized_parameters[key] = f"<{type(value).__name__}>"
618
+ continue
614
619
  try:
615
620
  serialized_parameters[key] = jsonable_encoder(value)
616
621
  except (TypeError, ValueError):
617
622
  logger.debug(
618
- f"Parameter {key!r} for flow {self.name!r} is of unserializable "
619
- f"type {type(value).__name__!r} and will not be stored "
623
+ f"Parameter {key!r} for flow {self.name!r} is unserializable. "
624
+ f"Type {type(value).__name__!r} and will not be stored "
620
625
  "in the backend."
621
626
  )
622
627
  serialized_parameters[key] = f"<{type(value).__name__}>"
@@ -1 +1 @@
1
- from .store import RecordStore
1
+ from .base import RecordStore
@@ -0,0 +1,167 @@
1
+ import os
2
+ import socket
3
+ import threading
4
+ from contextlib import contextmanager
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+
8
+ from prefect.results import BaseResult
9
+
10
+
11
+ class RecordStore:
12
+ def read(self, key: str):
13
+ raise NotImplementedError
14
+
15
+ def write(self, key: str, value: dict):
16
+ raise NotImplementedError
17
+
18
+ def exists(self, key: str) -> bool:
19
+ return False
20
+
21
+ def acquire_lock(
22
+ self,
23
+ key: str,
24
+ holder: Optional[str] = None,
25
+ acquire_timeout: Optional[float] = None,
26
+ hold_timeout: Optional[float] = None,
27
+ ) -> bool:
28
+ """
29
+ Acquire a lock for a transaction record with the given key. Will block other
30
+ actors from updating this transaction record until the lock is
31
+ released.
32
+
33
+ Args:
34
+ key: Unique identifier for the transaction record.
35
+ holder: Unique identifier for the holder of the lock. If not provided,
36
+ a default holder based on the current host, process, and thread will
37
+ be used.
38
+ acquire_timeout: Max number of seconds to wait for the record to become
39
+ available if it is locked while attempting to acquire a lock. Pass 0
40
+ to attempt to acquire a lock without waiting. Blocks indefinitely by
41
+ default.
42
+ hold_timeout: Max number of seconds to hold the lock for. Holds the lock
43
+ indefinitely by default.
44
+
45
+ Returns:
46
+ bool: True if the lock was successfully acquired; False otherwise.
47
+ """
48
+ raise NotImplementedError
49
+
50
+ def release_lock(self, key: str, holder: Optional[str] = None):
51
+ """
52
+ Releases the lock on the corresponding transaction record.
53
+
54
+ Args:
55
+ key: Unique identifier for the transaction record.
56
+ holder: Unique identifier for the holder of the lock. Must match the
57
+ holder provided when acquiring the lock.
58
+ """
59
+ raise NotImplementedError
60
+
61
+ def is_locked(self, key: str) -> bool:
62
+ """
63
+ Simple check to see if the corresponding record is currently locked.
64
+
65
+ Args:
66
+ key: Unique identifier for the transaction record.
67
+
68
+ Returns:
69
+ True is the record is locked; False otherwise.
70
+ """
71
+ raise NotImplementedError
72
+
73
+ def is_lock_holder(self, key: str, holder: Optional[str] = None) -> bool:
74
+ """
75
+ Check if the current holder is the lock holder for the transaction record.
76
+
77
+ Args:
78
+ key: Unique identifier for the transaction record.
79
+ holder: Unique identifier for the holder of the lock. If not provided,
80
+ a default holder based on the current host, process, and thread will
81
+ be used.
82
+
83
+ Returns:
84
+ bool: True if the current holder is the lock holder; False otherwise.
85
+ """
86
+ raise NotImplementedError
87
+
88
+ def wait_for_lock(self, key: str, timeout: Optional[float] = None) -> bool:
89
+ """
90
+ Wait for the corresponding transaction record to become free.
91
+
92
+ Args:
93
+ key: Unique identifier for the transaction record.
94
+ timeout: Maximum time to wait. None means to wait indefinitely.
95
+
96
+ Returns:
97
+ bool: True if the lock becomes free within the timeout; False
98
+ otherwise.
99
+ """
100
+ ...
101
+
102
+ @staticmethod
103
+ def generate_default_holder() -> str:
104
+ """
105
+ Generate a default holder string using hostname, PID, and thread ID.
106
+
107
+ Returns:
108
+ str: A unique identifier string.
109
+ """
110
+ hostname = socket.gethostname()
111
+ pid = os.getpid()
112
+ thread_name = threading.current_thread().name
113
+ thread_id = threading.get_ident()
114
+ return f"{hostname}:{pid}:{thread_id}:{thread_name}"
115
+
116
+ @contextmanager
117
+ def lock(
118
+ self,
119
+ key: str,
120
+ holder: Optional[str] = None,
121
+ acquire_timeout: Optional[float] = None,
122
+ hold_timeout: Optional[float] = None,
123
+ ):
124
+ """
125
+ Context manager to lock the transaction record during the execution
126
+ of the nested code block.
127
+
128
+ Args:
129
+ key: Unique identifier for the transaction record.
130
+ holder: Unique identifier for the holder of the lock. If not provided,
131
+ a default holder based on the current host, process, and thread will
132
+ be used.
133
+ acquire_timeout: Max number of seconds to wait for the record to become
134
+ available if it is locked while attempting to acquire a lock. Pass 0
135
+ to attempt to acquire a lock without waiting. Blocks indefinitely by
136
+ default.
137
+ hold_timeout: Max number of seconds to hold the lock for. Holds the lock
138
+ indefinitely by default.
139
+
140
+ Example:
141
+ Hold a lock while during an operation:
142
+ ```python
143
+ with TransactionRecord(key="my-transaction-record-key").lock():
144
+ do_stuff()
145
+ ```
146
+ """
147
+ self.acquire_lock(
148
+ key=key,
149
+ holder=holder,
150
+ acquire_timeout=acquire_timeout,
151
+ hold_timeout=hold_timeout,
152
+ )
153
+
154
+ try:
155
+ yield
156
+ finally:
157
+ self.release_lock(key=key, holder=holder)
158
+
159
+
160
+ @dataclass
161
+ class TransactionRecord:
162
+ """
163
+ A dataclass representation of a transaction record.
164
+ """
165
+
166
+ key: str
167
+ result: Optional[BaseResult] = None
@@ -0,0 +1,165 @@
1
+ import threading
2
+ from typing import Dict, Optional, TypedDict
3
+
4
+ from prefect.results import BaseResult
5
+
6
+ from .base import RecordStore, TransactionRecord
7
+
8
+
9
+ class _LockInfo(TypedDict):
10
+ """
11
+ A dictionary containing information about a lock.
12
+
13
+ Attributes:
14
+ holder: The holder of the lock.
15
+ lock: The lock object.
16
+ expiration_timer: The timer for the lock expiration
17
+ """
18
+
19
+ holder: str
20
+ lock: threading.Lock
21
+ expiration_timer: Optional[threading.Timer]
22
+
23
+
24
+ class MemoryRecordStore(RecordStore):
25
+ """
26
+ A record store that stores data in memory.
27
+ """
28
+
29
+ _instance = None
30
+
31
+ def __new__(cls, *args, **kwargs):
32
+ if cls._instance is None:
33
+ cls._instance = super().__new__(cls)
34
+ return cls._instance
35
+
36
+ def __init__(self):
37
+ self._locks_dict_lock = threading.Lock()
38
+ self._locks: Dict[str, _LockInfo] = {}
39
+ self._records: Dict[str, TransactionRecord] = {}
40
+
41
+ def read(self, key: str) -> Optional[TransactionRecord]:
42
+ return self._records.get(key)
43
+
44
+ def write(self, key: str, value: BaseResult, holder: Optional[str] = None) -> None:
45
+ holder = holder or self.generate_default_holder()
46
+
47
+ with self._locks_dict_lock:
48
+ if self.is_locked(key) and not self.is_lock_holder(key, holder):
49
+ raise ValueError(
50
+ f"Cannot write to transaction with key {key} because it is locked by another holder."
51
+ )
52
+ self._records[key] = TransactionRecord(key=key, result=value)
53
+
54
+ def exists(self, key: str) -> bool:
55
+ return key in self._records
56
+
57
+ def _expire_lock(self, key: str):
58
+ """
59
+ Expire the lock for the given key.
60
+
61
+ Used as a callback for the expiration timer of a lock.
62
+
63
+ Args:
64
+ key: The key of the lock to expire.
65
+ """
66
+ with self._locks_dict_lock:
67
+ if key in self._locks:
68
+ lock_info = self._locks[key]
69
+ if lock_info["lock"].locked():
70
+ lock_info["lock"].release()
71
+ if lock_info["expiration_timer"]:
72
+ lock_info["expiration_timer"].cancel()
73
+ del self._locks[key]
74
+
75
+ def acquire_lock(
76
+ self,
77
+ key: str,
78
+ holder: Optional[str] = None,
79
+ acquire_timeout: Optional[float] = None,
80
+ hold_timeout: Optional[float] = None,
81
+ ) -> bool:
82
+ holder = holder or self.generate_default_holder()
83
+ with self._locks_dict_lock:
84
+ if key not in self._locks:
85
+ lock = threading.Lock()
86
+ lock.acquire()
87
+ expiration_timer = None
88
+ if hold_timeout is not None:
89
+ expiration_timer = threading.Timer(
90
+ hold_timeout, self._expire_lock, args=(key,)
91
+ )
92
+ expiration_timer.start()
93
+ self._locks[key] = _LockInfo(
94
+ holder=holder, lock=lock, expiration_timer=expiration_timer
95
+ )
96
+ return True
97
+ elif self._locks[key]["holder"] == holder:
98
+ return True
99
+ else:
100
+ existing_lock_info = self._locks[key]
101
+
102
+ if acquire_timeout is not None:
103
+ existing_lock_acquired = existing_lock_info["lock"].acquire(
104
+ timeout=acquire_timeout
105
+ )
106
+ else:
107
+ existing_lock_acquired = existing_lock_info["lock"].acquire()
108
+
109
+ if existing_lock_acquired:
110
+ with self._locks_dict_lock:
111
+ if (
112
+ expiration_timer := existing_lock_info["expiration_timer"]
113
+ ) is not None:
114
+ expiration_timer.cancel()
115
+ expiration_timer = None
116
+ if hold_timeout is not None:
117
+ expiration_timer = threading.Timer(
118
+ hold_timeout, self._expire_lock, args=(key,)
119
+ )
120
+ expiration_timer.start()
121
+ self._locks[key] = _LockInfo(
122
+ holder=holder,
123
+ lock=existing_lock_info["lock"],
124
+ expiration_timer=expiration_timer,
125
+ )
126
+ return True
127
+ return False
128
+
129
+ def release_lock(self, key: str, holder: Optional[str] = None) -> None:
130
+ holder = holder or self.generate_default_holder()
131
+ with self._locks_dict_lock:
132
+ if key in self._locks and self._locks[key]["holder"] == holder:
133
+ if (
134
+ expiration_timer := self._locks[key]["expiration_timer"]
135
+ ) is not None:
136
+ expiration_timer.cancel()
137
+ self._locks[key]["lock"].release()
138
+ del self._locks[key]
139
+ else:
140
+ raise ValueError(
141
+ f"No lock held by {holder} for transaction with key {key}"
142
+ )
143
+
144
+ def is_locked(self, key: str) -> bool:
145
+ return key in self._locks and self._locks[key]["lock"].locked()
146
+
147
+ def is_lock_holder(self, key: str, holder: Optional[str] = None) -> bool:
148
+ holder = holder or self.generate_default_holder()
149
+ lock_info = self._locks.get(key)
150
+ return (
151
+ lock_info is not None
152
+ and lock_info["lock"].locked()
153
+ and lock_info["holder"] == holder
154
+ )
155
+
156
+ def wait_for_lock(self, key: str, timeout: Optional[float] = None) -> bool:
157
+ if lock := self._locks.get(key, {}).get("lock"):
158
+ if timeout is not None:
159
+ lock_acquired = lock.acquire(timeout=timeout)
160
+ else:
161
+ lock_acquired = lock.acquire()
162
+ if lock_acquired:
163
+ lock.release()
164
+ return lock_acquired
165
+ return True
@@ -6,7 +6,7 @@ import pendulum
6
6
  from prefect.results import BaseResult, PersistedResult, ResultFactory
7
7
  from prefect.utilities.asyncutils import run_coro_as_sync
8
8
 
9
- from .store import RecordStore
9
+ from .base import RecordStore
10
10
 
11
11
 
12
12
  @dataclass
prefect/task_engine.py CHANGED
@@ -211,6 +211,9 @@ class BaseTaskRunEngine(Generic[P, R]):
211
211
  return task_run.state.is_running() or task_run.state.is_scheduled()
212
212
 
213
213
  def log_finished_message(self):
214
+ if not self.task_run:
215
+ return
216
+
214
217
  # If debugging, use the more complete `repr` than the usual `str` description
215
218
  display_state = repr(self.state) if PREFECT_DEBUG_MODE else str(self.state)
216
219
  level = logging.INFO if self.state.is_completed() else logging.ERROR
@@ -363,7 +366,7 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
363
366
  self.task_run.run_count += 1
364
367
 
365
368
  flow_run_context = FlowRunContext.get()
366
- if flow_run_context:
369
+ if flow_run_context and flow_run_context.flow_run:
367
370
  # Carry forward any task run information from the flow run
368
371
  flow_run = flow_run_context.flow_run
369
372
  self.task_run.flow_run_run_count = flow_run.run_count
@@ -622,21 +625,24 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
622
625
 
623
626
  self.logger = task_run_logger(task_run=self.task_run, task=self.task) # type: ignore
624
627
 
625
- if not PREFECT_EXPERIMENTAL_ENABLE_CLIENT_SIDE_TASK_ORCHESTRATION:
626
- # update the task run name if necessary
627
- if not self._task_name_set and self.task.task_run_name:
628
- task_run_name = _resolve_custom_task_run_name(
629
- task=self.task, parameters=self.parameters
630
- )
628
+ # update the task run name if necessary
629
+ if not self._task_name_set and self.task.task_run_name:
630
+ task_run_name = _resolve_custom_task_run_name(
631
+ task=self.task, parameters=self.parameters
632
+ )
633
+
634
+ if not PREFECT_EXPERIMENTAL_ENABLE_CLIENT_SIDE_TASK_ORCHESTRATION:
635
+ # update the task run name if necessary
631
636
  self.client.set_task_run_name(
632
637
  task_run_id=self.task_run.id, name=task_run_name
633
638
  )
634
- self.logger.extra["task_run_name"] = task_run_name
635
- self.logger.debug(
636
- f"Renamed task run {self.task_run.name!r} to {task_run_name!r}"
637
- )
638
- self.task_run.name = task_run_name
639
- self._task_name_set = True
639
+
640
+ self.logger.extra["task_run_name"] = task_run_name
641
+ self.logger.debug(
642
+ f"Renamed task run {self.task_run.name!r} to {task_run_name!r}"
643
+ )
644
+ self.task_run.name = task_run_name
645
+ self._task_name_set = True
640
646
  yield
641
647
 
642
648
  @contextmanager
@@ -655,21 +661,6 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
655
661
  self._is_started = True
656
662
  try:
657
663
  if PREFECT_EXPERIMENTAL_ENABLE_CLIENT_SIDE_TASK_ORCHESTRATION:
658
- from prefect.utilities.engine import (
659
- _resolve_custom_task_run_name,
660
- )
661
-
662
- task_run_name = (
663
- _resolve_custom_task_run_name(
664
- task=self.task, parameters=self.parameters
665
- )
666
- if self.task.task_run_name
667
- else None
668
- )
669
-
670
- if self.task_run and task_run_name:
671
- self.task_run.name = task_run_name
672
-
673
664
  if not self.task_run:
674
665
  self.task_run = run_coro_as_sync(
675
666
  self.task.create_local_run(
@@ -679,7 +670,6 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
679
670
  parent_task_run_context=TaskRunContext.get(),
680
671
  wait_for=self.wait_for,
681
672
  extra_task_inputs=dependencies,
682
- task_run_name=task_run_name,
683
673
  )
684
674
  )
685
675
  # Emit an event to capture that the task run was in the `PENDING` state.
@@ -1185,21 +1175,21 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1185
1175
 
1186
1176
  self.logger = task_run_logger(task_run=self.task_run, task=self.task) # type: ignore
1187
1177
 
1188
- if not PREFECT_EXPERIMENTAL_ENABLE_CLIENT_SIDE_TASK_ORCHESTRATION:
1189
- # update the task run name if necessary
1190
- if not self._task_name_set and self.task.task_run_name:
1191
- task_run_name = _resolve_custom_task_run_name(
1192
- task=self.task, parameters=self.parameters
1193
- )
1178
+ if not self._task_name_set and self.task.task_run_name:
1179
+ task_run_name = _resolve_custom_task_run_name(
1180
+ task=self.task, parameters=self.parameters
1181
+ )
1182
+ if not PREFECT_EXPERIMENTAL_ENABLE_CLIENT_SIDE_TASK_ORCHESTRATION:
1183
+ # update the task run name if necessary
1194
1184
  await self.client.set_task_run_name(
1195
1185
  task_run_id=self.task_run.id, name=task_run_name
1196
1186
  )
1197
- self.logger.extra["task_run_name"] = task_run_name
1198
- self.logger.debug(
1199
- f"Renamed task run {self.task_run.name!r} to {task_run_name!r}"
1200
- )
1201
- self.task_run.name = task_run_name
1202
- self._task_name_set = True
1187
+ self.logger.extra["task_run_name"] = task_run_name
1188
+ self.logger.debug(
1189
+ f"Renamed task run {self.task_run.name!r} to {task_run_name!r}"
1190
+ )
1191
+ self.task_run.name = task_run_name
1192
+ self._task_name_set = True
1203
1193
  yield
1204
1194
 
1205
1195
  @asynccontextmanager
@@ -1218,21 +1208,6 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1218
1208
  self._is_started = True
1219
1209
  try:
1220
1210
  if PREFECT_EXPERIMENTAL_ENABLE_CLIENT_SIDE_TASK_ORCHESTRATION:
1221
- from prefect.utilities.engine import (
1222
- _resolve_custom_task_run_name,
1223
- )
1224
-
1225
- task_run_name = (
1226
- _resolve_custom_task_run_name(
1227
- task=self.task, parameters=self.parameters
1228
- )
1229
- if self.task.task_run_name
1230
- else None
1231
- )
1232
-
1233
- if self.task_run and task_run_name:
1234
- self.task_run.name = task_run_name
1235
-
1236
1211
  if not self.task_run:
1237
1212
  self.task_run = await self.task.create_local_run(
1238
1213
  id=task_run_id,
@@ -1241,7 +1216,6 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1241
1216
  parent_task_run_context=TaskRunContext.get(),
1242
1217
  wait_for=self.wait_for,
1243
1218
  extra_task_inputs=dependencies,
1244
- task_run_name=task_run_name,
1245
1219
  )
1246
1220
  # Emit an event to capture that the task run was in the `PENDING` state.
1247
1221
  self._last_event = emit_task_run_state_change_event(
prefect/tasks.py CHANGED
@@ -814,7 +814,6 @@ class Task(Generic[P, R]):
814
814
  wait_for: Optional[Iterable[PrefectFuture]] = None,
815
815
  extra_task_inputs: Optional[Dict[str, Set[TaskRunInput]]] = None,
816
816
  deferred: bool = False,
817
- task_run_name: Optional[str] = None,
818
817
  ) -> TaskRun:
819
818
  if not PREFECT_EXPERIMENTAL_ENABLE_CLIENT_SIDE_TASK_ORCHESTRATION:
820
819
  raise RuntimeError(
@@ -839,12 +838,12 @@ class Task(Generic[P, R]):
839
838
  async with client:
840
839
  if not flow_run_context:
841
840
  dynamic_key = f"{self.task_key}-{str(uuid4().hex)}"
842
- task_run_name = task_run_name or self.name
841
+ task_run_name = self.name
843
842
  else:
844
843
  dynamic_key = _dynamic_key_for_task_run(
845
- context=flow_run_context, task=self
844
+ context=flow_run_context, task=self, stable=False
846
845
  )
847
- task_run_name = task_run_name or f"{self.name}-{dynamic_key}"
846
+ task_run_name = f"{self.name}-{dynamic_key[:3]}"
848
847
 
849
848
  if deferred:
850
849
  state = Scheduled()
prefect/transactions.py CHANGED
@@ -1,3 +1,4 @@
1
+ import copy
1
2
  import logging
2
3
  from contextlib import contextmanager
3
4
  from contextvars import ContextVar, Token
@@ -25,6 +26,7 @@ from prefect.results import (
25
26
  ResultFactory,
26
27
  get_default_result_storage,
27
28
  )
29
+ from prefect.utilities.annotations import NotSet
28
30
  from prefect.utilities.asyncutils import run_coro_as_sync
29
31
  from prefect.utilities.collections import AutoEnum
30
32
  from prefect.utilities.engine import _get_hook_name
@@ -72,8 +74,10 @@ class Transaction(ContextModel):
72
74
  def set(self, name: str, value: Any) -> None:
73
75
  self._stored_values[name] = value
74
76
 
75
- def get(self, name: str) -> Any:
77
+ def get(self, name: str, default: Any = NotSet) -> Any:
76
78
  if name not in self._stored_values:
79
+ if default is not NotSet:
80
+ return default
77
81
  raise ValueError(f"Could not retrieve value for unknown key: {name}")
78
82
  return self._stored_values.get(name)
79
83
 
@@ -104,6 +108,7 @@ class Transaction(ContextModel):
104
108
  # either inherit from parent or set a default of eager
105
109
  if parent:
106
110
  self.commit_mode = parent.commit_mode
111
+ self._stored_values = copy.deepcopy(parent._stored_values)
107
112
  else:
108
113
  self.commit_mode = CommitMode.LAZY
109
114
 
@@ -559,8 +559,12 @@ def propose_state_sync(
559
559
  )
560
560
 
561
561
 
562
- def _dynamic_key_for_task_run(context: FlowRunContext, task: Task) -> Union[int, str]:
563
- if context.detached: # this task is running on remote infrastructure
562
+ def _dynamic_key_for_task_run(
563
+ context: FlowRunContext, task: Task, stable: bool = True
564
+ ) -> Union[int, str]:
565
+ if (
566
+ stable is False or context.detached
567
+ ): # this task is running on remote infrastructure
564
568
  return str(uuid4())
565
569
  elif context.flow_run is None: # this is an autonomous task run
566
570
  context.task_run_dynamic_keys[task.task_key] = getattr(
prefect/workers/base.py CHANGED
@@ -930,7 +930,12 @@ class BaseWorker(abc.ABC):
930
930
 
931
931
  deployment_vars = deployment.job_variables or {}
932
932
  flow_run_vars = flow_run.job_variables or {}
933
- job_variables = {**deployment_vars, **flow_run_vars}
933
+ job_variables = {**deployment_vars}
934
+
935
+ # merge environment variables carefully, otherwise full override
936
+ if isinstance(job_variables.get("env"), dict):
937
+ job_variables["env"].update(flow_run_vars.pop("env", {}))
938
+ job_variables.update(flow_run_vars)
934
939
 
935
940
  configuration = await self.job_configuration.from_template_and_values(
936
941
  base_job_template=self._work_pool.base_job_template,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prefect-client
3
- Version: 3.0.0rc16
3
+ Version: 3.0.0rc18
4
4
  Summary: Workflow orchestration and management.
5
5
  Home-page: https://www.prefect.io
6
6
  Author: Prefect Technologies, Inc.
@@ -4,14 +4,14 @@ prefect/_version.py,sha256=I9JsXwt7BjAAbMEZgtmE3a6dJ2jqV-wqWto9D6msb3k,24597
4
4
  prefect/agent.py,sha256=BOVVY5z-vUIQ2u8LwMTXDaNys2fjOZSS5YGDwJmTQjI,230
5
5
  prefect/artifacts.py,sha256=wet3coxBtqK0914uTf-slYpXRVP0mjbZn804hXB-RS4,13011
6
6
  prefect/automations.py,sha256=NlQ62GPJzy-gnWQqX7c6CQJKw7p60WLGDAFcy82vtg4,5613
7
- prefect/cache_policies.py,sha256=uEKNGO-PJ3N35B2tjhRDtQULN6ok72D9raIoJaUyXk0,6365
7
+ prefect/cache_policies.py,sha256=779dI23HIPmrtImx9X6eVEH5jc9M7wE9T2isivNo8uM,6570
8
8
  prefect/context.py,sha256=Q04o0F1zc9O9y7H0Y6po1hvyajrviYAzuQmpKdNvgbM,21859
9
9
  prefect/engine.py,sha256=BpmDbe6miZcTl1vRkxfCPYcWSXADLigGPCagFwucMz0,1976
10
10
  prefect/exceptions.py,sha256=3s69Z_IC3HKF6BKxcHrMPXkKdYwfbEfaTjy4-5LOtQ0,11132
11
11
  prefect/filesystems.py,sha256=rbFvlqHXeeo71nK1Y5I0-ucmGOYUcdkbb6N2vpsRcWE,17229
12
12
  prefect/flow_engine.py,sha256=c8mIffc57zLtHFRo4sVtQOXGihVA_y2mZiXYzjJlOHY,29445
13
13
  prefect/flow_runs.py,sha256=EaXRIQTOnwnA0fO7_EjwafFRmS57K_CRy0Xsz3JDIhc,16070
14
- prefect/flows.py,sha256=Sl3m8Q3mXwY1MSgJSeViiXag4jtUBN7stMP1Fs4iepo,88039
14
+ prefect/flows.py,sha256=9TQZaDYSY3Ao72YHF5pvmEjXD8Z5vgTp2aLEAEWiJ2Y,88326
15
15
  prefect/futures.py,sha256=Zt5U7PnNpKUQuyfAhWAZZxpG0hQ6HXuA4KVg6E9sQf8,16208
16
16
  prefect/main.py,sha256=bab5nBn37a6gmxdPbTlRS2a9Cf0KY0GaCotDOSbcQ7M,1930
17
17
  prefect/manifests.py,sha256=477XcmfdC_yE81wT6zIAKnEUEJ0lH9ZLfOVSgX2FohE,676
@@ -22,12 +22,12 @@ prefect/results.py,sha256=3mVkVWZn_VSQ9Pik79StNy113rB_SEiP83SdoUsFvTM,24635
22
22
  prefect/serializers.py,sha256=Lo41EM0_qGzcfB_63390Izeo3DdK6cY6VZfxa9hpSGQ,8712
23
23
  prefect/settings.py,sha256=RpO6XMM2qf6KC_rQMHmQpcjul1t2rZmLOkK89Ta3OcM,72076
24
24
  prefect/states.py,sha256=lw22xucH46cN9stkxiV9ByIvq689mH5iL3gErri-Y18,22207
25
- prefect/task_engine.py,sha256=z5raK4OLgCRp6cubrTDL0XZQsx8Z5xTFwKn2QnITGcA,64053
25
+ prefect/task_engine.py,sha256=KIKspqIJpmT_8egEufaDThb1SvDGA9aPwPF5Y87UHe8,62799
26
26
  prefect/task_runners.py,sha256=W1n0yMwbDIqnvffFVJADo9MGEbLaYkzWk52rqgnkMY4,15019
27
27
  prefect/task_runs.py,sha256=jkaQOkRKOHS8fgHUijteriFpjMSKv4zldn1D8tZHkUI,8777
28
28
  prefect/task_worker.py,sha256=DX4NYERghB8RZeFleZE0xOq3yJVunjUaAKHtiz8wuRo,17992
29
- prefect/tasks.py,sha256=tpYQhH4wZcOeVTOOGus0Med4prLlmMM2jSf3TKuFdUQ,68230
30
- prefect/transactions.py,sha256=YyWuY99mBeEsY8JGR3_eyWbFIUxWIzbHfU2UMBKTaik,11038
29
+ prefect/tasks.py,sha256=AGh4D8zZhvmmZiixeFjda11RjhAMlK3Myr0ABaIapCQ,68169
30
+ prefect/transactions.py,sha256=6j69ClTNr10XxrsVu9QmsRU6vYU-_14umk_fvAQsBNM,11266
31
31
  prefect/variables.py,sha256=-t5LVY0N-K4f0fa6YwruVVQqwnU3fGWBMYXXE32XPkA,4821
32
32
  prefect/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  prefect/_internal/_logging.py,sha256=HvNHY-8P469o5u4LYEDBTem69XZEt1QUeUaLToijpak,810
@@ -62,7 +62,7 @@ prefect/_internal/schemas/serializers.py,sha256=G_RGHfObjisUiRvd29p-zc6W4bwt5rE1
62
62
  prefect/_internal/schemas/validators.py,sha256=Y8bHb3EsLJTiHsffg_TPbknj0Nmln8vd6qySLFbfGzY,26546
63
63
  prefect/blocks/__init__.py,sha256=BUfh6gIwA6HEjRyVCAiv0he3M1zfM-oY-JrlBfeWeY8,182
64
64
  prefect/blocks/abstract.py,sha256=YLzCaf3yXv6wFCF5ZqCIHJNwH7fME1rLxC-SijARHzk,16319
65
- prefect/blocks/core.py,sha256=EAvmPga9fyUfK9QW_HFLSit0dn6Szh_KbDK3n8HX4q0,52155
65
+ prefect/blocks/core.py,sha256=b3cdh-MsEi2BfE8EZ2jLyzf1vPWMmHNp4AboC-vSthc,52165
66
66
  prefect/blocks/fields.py,sha256=1m507VVmkpOnMF_7N-qboRjtw4_ceIuDneX3jZ3Jm54,63
67
67
  prefect/blocks/notifications.py,sha256=_ARWqq0NHFPxaaW27W6VieBVBwAKELeHtAkHXXP3R_g,29076
68
68
  prefect/blocks/redis.py,sha256=GUKYyx2QLtyNvgf5FT_dJxbgQcOzWCja3I23J1-AXhM,5629
@@ -77,12 +77,12 @@ prefect/client/orchestration.py,sha256=G3zkz3HTfy9ZMELKAi8HRqvybOhdlOsia2Vt7ElWE
77
77
  prefect/client/subscriptions.py,sha256=J9uK9NGHO4VX4Y3NGgBJ4pIG_0cf-dJWPhF3f3PGYL4,3388
78
78
  prefect/client/utilities.py,sha256=89fmza0cRMOayxgXRdO51TKb11TczJ0ByOZmcZVrt44,3286
79
79
  prefect/client/schemas/__init__.py,sha256=KlyqFV-hMulMkNstBn_0ijoHoIwJZaBj6B1r07UmgvE,607
80
- prefect/client/schemas/actions.py,sha256=i0izw0JPdlvAeEC2YEbmskr7kU0-tyzEwEtCKXMPi74,28101
81
- prefect/client/schemas/filters.py,sha256=_Ibx8VDICS0wkqo-M8zlpOyAbkrRw8sfgqyD9_x2Vvc,34881
82
- prefect/client/schemas/objects.py,sha256=PNxOTBwQDQ57703i6ZrV_kxYDSuJSrJ3O_42hDwOliA,53296
83
- prefect/client/schemas/responses.py,sha256=3Y-md6uXCF7X8gcZYvvKqcu5CXqvJfkJyfocHFypp9g,14637
80
+ prefect/client/schemas/actions.py,sha256=LJrioOmj34drliWEMF65_6xZgtv9UxyJgB5ksi1aXhU,28484
81
+ prefect/client/schemas/filters.py,sha256=Pu0wB58_dsNwTeItpQdYII2mwGe0VlLV3ULsuI2PyCg,35648
82
+ prefect/client/schemas/objects.py,sha256=52eJM22Di5C5yuyfbd1mv4j2P6GpAWJlu2b1b8lJmUE,53426
83
+ prefect/client/schemas/responses.py,sha256=J61c8ZqrxfOjKkdf6MLLrRBScsS0tql6Q2SPVr04Ids,14767
84
84
  prefect/client/schemas/schedules.py,sha256=8rpqjOYtknu2-1n5_WD4cOplgu93P3mCyX86B22LfL4,13070
85
- prefect/client/schemas/sorting.py,sha256=EIQ6FUjUWMwk6fn6ckVLQLXOP-GI5kce7ftjUkDFWV0,2490
85
+ prefect/client/schemas/sorting.py,sha256=L-2Mx-igZPtsUoRUguTcG3nIEstMEMPD97NwPM2Ox5s,2579
86
86
  prefect/client/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
87
  prefect/client/types/flexible_schedule_list.py,sha256=F1VFAXGLM89dJRBLnVsxwAMGLmrRF2i81FirEMpbB5s,368
88
88
  prefect/concurrency/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -135,9 +135,10 @@ prefect/logging/handlers.py,sha256=eIf-0LFH8XUu8Ybnc3LXoocSsa8M8EdAIwbPTVFzZjI,1
135
135
  prefect/logging/highlighters.py,sha256=BpSXOy0n3lFVvlKWa7jC-HetAiClFi9jnQtEq5-rgok,1681
136
136
  prefect/logging/loggers.py,sha256=xmJ4GIuSXyFV4yMMDK-VokMFGbfcX64PLPyzdM_aV9c,11544
137
137
  prefect/logging/logging.yml,sha256=UkEewf0c3_dURI2uCU4RrxkhI5Devoa1s93fl7hilcg,3160
138
- prefect/records/__init__.py,sha256=7q-lwyevfVgb5S7K9frzawmiJmpZ5ET0m5yXIHBYcVA,31
139
- prefect/records/result_store.py,sha256=6Yh9zqqXMWjn0qWSfcjQBSfXCM7jVg9pve5TVsOodDc,1734
140
- prefect/records/store.py,sha256=eQM1p2vZDshXZYg6SkJwL-DP3kUehL_Zgs8xa2-0DZs,224
138
+ prefect/records/__init__.py,sha256=rJJhaJBa0AWu63fJhtB-yHBi64qL6p4svo7F0qvm2sc,30
139
+ prefect/records/base.py,sha256=yJet1guDtA7RWC2nZBvggeI_JF3NaX_7WODDwTauBvs,5545
140
+ prefect/records/memory.py,sha256=dgNNOrib6cbe8mjGELxpyOaHCFFtco_DE9ULK54_Ca0,5768
141
+ prefect/records/result_store.py,sha256=Ac_zlLcKfEzXRh2O0hTP0jmgZTmBRUE_JvG69NxH7Ao,1733
141
142
  prefect/runner/__init__.py,sha256=7U-vAOXFkzMfRz1q8Uv6Otsvc0OrPYLLP44srwkJ_8s,89
142
143
  prefect/runner/runner.py,sha256=yrBG5fCO7dCpE-lwPOkx8ToBtfJ3-BKQIUm9eNbjA2g,47388
143
144
  prefect/runner/server.py,sha256=2o5vhrL7Zbn-HBStWhCjqqViex5Ye9GiQ1EW9RSEzdo,10500
@@ -161,7 +162,7 @@ prefect/utilities/compat.py,sha256=mNQZDnzyKaOqy-OV-DnmH_dc7CNF5nQgW_EsA4xMr7g,9
161
162
  prefect/utilities/context.py,sha256=BThuUW94-IYgFYTeMIM9KMo8ShT3oiI7w5ajZHzU1j0,1377
162
163
  prefect/utilities/dispatch.py,sha256=c8G-gBo7hZlyoD7my9nO50Rzy8Retk-np5WGq9_E2AM,5856
163
164
  prefect/utilities/dockerutils.py,sha256=kRozGQ7JO6Uxl-ljWtDryzxhf96rHL78aHYDh255Em4,20324
164
- prefect/utilities/engine.py,sha256=KNr6VVPL_EBxMAc06bAV4yHpF4lkaZKndXG6BodW3T0,31659
165
+ prefect/utilities/engine.py,sha256=_rfhPBNuIghCGd1-AN_Csg_Ar046GO3n48KB_EpHHbU,31721
165
166
  prefect/utilities/filesystem.py,sha256=frAyy6qOeYa7c-jVbEUGZQEe6J1yF8I_SvUepPd59gI,4415
166
167
  prefect/utilities/hashing.py,sha256=EOwZLmoIZImuSTxAvVqInabxJ-4RpEfYeg9e2EDQF8o,1752
167
168
  prefect/utilities/importtools.py,sha256=Wo4Tj9hSf7gghP83MxW3w9FK3jkaGKPEobDYjabPqT0,19389
@@ -181,14 +182,14 @@ prefect/utilities/schema_tools/__init__.py,sha256=KsFsTEHQqgp89TkDpjggkgBBywoHQP
181
182
  prefect/utilities/schema_tools/hydration.py,sha256=Nitnmr35Mcn5z9NXIvh9DuZW5nCZxpjyMc9RFawMsgs,8376
182
183
  prefect/utilities/schema_tools/validation.py,sha256=2GCjxwApTFwzey40ul9OkcAXrU3r-kWK__9ucMo0qbk,9744
183
184
  prefect/workers/__init__.py,sha256=8dP8SLZbWYyC_l9DRTQSE3dEbDgns5DZDhxkp_NfsbQ,35
184
- prefect/workers/base.py,sha256=gjv-ZxwJOSr9mytY5bVwj8rtFriLGacGghQhwcX9hQI,43457
185
+ prefect/workers/base.py,sha256=T1G4EkiJlK7n1PZthmYseuzOr21pQypwZ2lQU1qElFA,43683
185
186
  prefect/workers/block.py,sha256=BOVVY5z-vUIQ2u8LwMTXDaNys2fjOZSS5YGDwJmTQjI,230
186
187
  prefect/workers/cloud.py,sha256=BOVVY5z-vUIQ2u8LwMTXDaNys2fjOZSS5YGDwJmTQjI,230
187
188
  prefect/workers/process.py,sha256=t1f1EYRoPL5B25KbLgUX2b5q-lCCAXb2Gpf6T2M9WfY,19822
188
189
  prefect/workers/server.py,sha256=lgh2FfSuaNU7b6HPxSFm8JtKvAvHsZGkiOo4y4tW1Cw,2022
189
190
  prefect/workers/utilities.py,sha256=VfPfAlGtTuDj0-Kb8WlMgAuOfgXCdrGAnKMapPSBrwc,2483
190
- prefect_client-3.0.0rc16.dist-info/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
191
- prefect_client-3.0.0rc16.dist-info/METADATA,sha256=u1Slx4EdzeZnEU0qZ_7V28FsWgCbcfRsjYVCk-1KNcY,7404
192
- prefect_client-3.0.0rc16.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
193
- prefect_client-3.0.0rc16.dist-info/top_level.txt,sha256=MJZYJgFdbRc2woQCeB4vM6T33tr01TmkEhRcns6H_H4,8
194
- prefect_client-3.0.0rc16.dist-info/RECORD,,
191
+ prefect_client-3.0.0rc18.dist-info/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
192
+ prefect_client-3.0.0rc18.dist-info/METADATA,sha256=-ar0iFrvdZa5j0kUwS1uJn09jfVzrdRmSxSECZq8A5Q,7404
193
+ prefect_client-3.0.0rc18.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
194
+ prefect_client-3.0.0rc18.dist-info/top_level.txt,sha256=MJZYJgFdbRc2woQCeB4vM6T33tr01TmkEhRcns6H_H4,8
195
+ prefect_client-3.0.0rc18.dist-info/RECORD,,
prefect/records/store.py DELETED
@@ -1,9 +0,0 @@
1
- class RecordStore:
2
- def read(self, key: str):
3
- raise NotImplementedError
4
-
5
- def write(self, key: str, value: dict):
6
- raise NotImplementedError
7
-
8
- def exists(self, key: str) -> bool:
9
- return False