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.
- prefect/__init__.py +0 -3
- prefect/_internal/compatibility/migration.py +1 -1
- prefect/artifacts.py +1 -1
- prefect/blocks/core.py +8 -5
- prefect/blocks/notifications.py +10 -10
- prefect/blocks/system.py +52 -16
- prefect/blocks/webhook.py +3 -1
- prefect/client/cloud.py +57 -7
- prefect/client/collections.py +1 -1
- prefect/client/orchestration.py +68 -7
- prefect/client/schemas/objects.py +40 -2
- prefect/concurrency/asyncio.py +8 -2
- prefect/concurrency/services.py +16 -6
- prefect/concurrency/sync.py +4 -1
- prefect/context.py +7 -9
- prefect/deployments/runner.py +3 -3
- prefect/exceptions.py +12 -0
- prefect/filesystems.py +5 -3
- prefect/flow_engine.py +16 -10
- prefect/flows.py +2 -4
- prefect/futures.py +2 -1
- prefect/locking/__init__.py +0 -0
- prefect/locking/memory.py +213 -0
- prefect/locking/protocol.py +122 -0
- prefect/logging/handlers.py +4 -1
- prefect/main.py +8 -6
- prefect/records/filesystem.py +4 -2
- prefect/records/result_store.py +12 -6
- prefect/results.py +768 -363
- prefect/settings.py +24 -10
- prefect/states.py +82 -27
- prefect/task_engine.py +51 -26
- prefect/task_worker.py +6 -4
- prefect/tasks.py +24 -6
- prefect/transactions.py +57 -36
- prefect/utilities/annotations.py +4 -3
- prefect/utilities/asyncutils.py +1 -1
- prefect/utilities/callables.py +1 -3
- prefect/utilities/dispatch.py +16 -11
- prefect/utilities/schema_tools/hydration.py +13 -0
- prefect/variables.py +34 -24
- prefect/workers/base.py +78 -18
- prefect/workers/process.py +1 -3
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/METADATA +2 -2
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/RECORD +48 -46
- prefect/manifests.py +0 -21
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/WHEEL +0 -0
- {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
|
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.
|
25
|
-
|
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:
|
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) ->
|
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
|
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
|
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
|
-
|
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
|
-
|
260
|
-
|
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
|
-
|
265
|
-
|
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:
|
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
|
-
|
353
|
-
flow_run_context, "
|
375
|
+
existing_store = getattr(task_run_context, "result_store", None) or getattr(
|
376
|
+
flow_run_context, "result_store", None
|
354
377
|
)
|
355
378
|
|
356
|
-
|
357
|
-
if
|
358
|
-
|
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
|
366
|
-
|
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
|
-
|
375
|
-
|
376
|
-
|
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
|
401
|
+
from prefect.records.result_store import ResultRecordStore
|
381
402
|
|
382
|
-
store =
|
383
|
-
|
403
|
+
store = ResultRecordStore(
|
404
|
+
result_store=new_store,
|
384
405
|
)
|
385
406
|
|
386
407
|
try:
|
prefect/utilities/annotations.py
CHANGED
@@ -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:
|
25
|
-
if
|
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:
|
prefect/utilities/asyncutils.py
CHANGED
prefect/utilities/callables.py
CHANGED
@@ -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
|
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:
|
prefect/utilities/dispatch.py
CHANGED
@@ -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
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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
|
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
|
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(
|
13
|
+
class Variable(BaseModel):
|
14
14
|
"""
|
15
|
-
Variables are named, mutable
|
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
|
-
|
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
|
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=
|
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
|
-
|
69
|
-
variable=VariableRequest(**var_dict)
|
70
|
-
)
|
84
|
+
await client.create_variable(variable=VariableCreate(**var_dict))
|
71
85
|
|
72
|
-
return
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
849
|
+
run_logger.info(f"Completed submission of flow run '{flow_run.id}'")
|
841
850
|
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
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
|
-
|
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
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
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 =
|
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:
|
prefect/workers/process.py
CHANGED
@@ -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.
|
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/
|
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
|