prefect-client 3.0.0rc20__py3-none-any.whl → 3.0.2__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/_internal/compatibility/deprecated.py +1 -1
- prefect/_internal/compatibility/migration.py +1 -1
- prefect/artifacts.py +1 -1
- prefect/blocks/core.py +3 -4
- prefect/blocks/notifications.py +31 -10
- prefect/blocks/system.py +4 -4
- prefect/blocks/webhook.py +11 -1
- prefect/client/cloud.py +2 -1
- prefect/client/orchestration.py +93 -21
- prefect/client/schemas/actions.py +2 -2
- prefect/client/schemas/objects.py +24 -6
- prefect/client/types/flexible_schedule_list.py +1 -1
- prefect/concurrency/asyncio.py +45 -6
- prefect/concurrency/services.py +1 -1
- prefect/concurrency/sync.py +21 -27
- prefect/concurrency/v1/asyncio.py +3 -0
- prefect/concurrency/v1/sync.py +4 -5
- prefect/context.py +11 -9
- prefect/deployments/runner.py +4 -3
- prefect/events/actions.py +6 -0
- prefect/exceptions.py +6 -0
- prefect/filesystems.py +5 -3
- prefect/flow_engine.py +22 -11
- prefect/flows.py +0 -2
- prefect/futures.py +2 -1
- prefect/locking/__init__.py +0 -0
- prefect/locking/filesystem.py +243 -0
- prefect/locking/memory.py +213 -0
- prefect/locking/protocol.py +122 -0
- prefect/logging/handlers.py +0 -2
- prefect/logging/loggers.py +0 -18
- prefect/logging/logging.yml +1 -0
- prefect/main.py +19 -5
- prefect/records/base.py +12 -0
- prefect/records/filesystem.py +10 -4
- prefect/records/memory.py +6 -0
- prefect/records/result_store.py +18 -6
- prefect/results.py +702 -205
- prefect/runner/runner.py +74 -5
- prefect/settings.py +11 -4
- prefect/states.py +40 -23
- prefect/task_engine.py +39 -37
- prefect/task_worker.py +6 -4
- prefect/tasks.py +24 -6
- prefect/transactions.py +116 -54
- prefect/utilities/callables.py +1 -3
- prefect/utilities/engine.py +16 -8
- prefect/utilities/importtools.py +1 -0
- prefect/utilities/urls.py +70 -12
- prefect/variables.py +34 -24
- prefect/workers/base.py +14 -6
- prefect/workers/process.py +1 -3
- {prefect_client-3.0.0rc20.dist-info → prefect_client-3.0.2.dist-info}/METADATA +2 -2
- {prefect_client-3.0.0rc20.dist-info → prefect_client-3.0.2.dist-info}/RECORD +57 -53
- {prefect_client-3.0.0rc20.dist-info → prefect_client-3.0.2.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.0rc20.dist-info → prefect_client-3.0.2.dist-info}/WHEEL +0 -0
- {prefect_client-3.0.0rc20.dist-info → prefect_client-3.0.2.dist-info}/top_level.txt +0 -0
prefect/transactions.py
CHANGED
@@ -17,17 +17,19 @@ 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
|
20
|
+
from prefect.context import ContextModel
|
21
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.records.base import TransactionRecord
|
24
25
|
from prefect.results import (
|
25
26
|
BaseResult,
|
26
|
-
|
27
|
-
|
27
|
+
ResultRecord,
|
28
|
+
ResultStore,
|
29
|
+
get_result_store,
|
30
|
+
should_persist_result,
|
28
31
|
)
|
29
32
|
from prefect.utilities.annotations import NotSet
|
30
|
-
from prefect.utilities.asyncutils import run_coro_as_sync
|
31
33
|
from prefect.utilities.collections import AutoEnum
|
32
34
|
from prefect.utilities.engine import _get_hook_name
|
33
35
|
|
@@ -56,7 +58,7 @@ class Transaction(ContextModel):
|
|
56
58
|
A base model for transaction state.
|
57
59
|
"""
|
58
60
|
|
59
|
-
store:
|
61
|
+
store: Union[RecordStore, ResultStore, None] = None
|
60
62
|
key: Optional[str] = None
|
61
63
|
children: List["Transaction"] = Field(default_factory=list)
|
62
64
|
commit_mode: Optional[CommitMode] = None
|
@@ -70,19 +72,91 @@ class Transaction(ContextModel):
|
|
70
72
|
logger: Union[logging.Logger, logging.LoggerAdapter] = Field(
|
71
73
|
default_factory=partial(get_logger, "transactions")
|
72
74
|
)
|
75
|
+
write_on_commit: bool = True
|
73
76
|
_stored_values: Dict[str, Any] = PrivateAttr(default_factory=dict)
|
74
77
|
_staged_value: Any = None
|
75
78
|
__var__: ContextVar = ContextVar("transaction")
|
76
79
|
|
77
80
|
def set(self, name: str, value: Any) -> None:
|
81
|
+
"""
|
82
|
+
Set a stored value in the transaction.
|
83
|
+
|
84
|
+
Args:
|
85
|
+
name: The name of the value to set
|
86
|
+
value: The value to set
|
87
|
+
|
88
|
+
Examples:
|
89
|
+
Set a value for use later in the transaction:
|
90
|
+
```python
|
91
|
+
with transaction() as txn:
|
92
|
+
txn.set("key", "value")
|
93
|
+
...
|
94
|
+
assert txn.get("key") == "value"
|
95
|
+
```
|
96
|
+
"""
|
78
97
|
self._stored_values[name] = value
|
79
98
|
|
80
99
|
def get(self, name: str, default: Any = NotSet) -> Any:
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
100
|
+
"""
|
101
|
+
Get a stored value from the transaction.
|
102
|
+
|
103
|
+
Child transactions will return values from their parents unless a value with
|
104
|
+
the same name is set in the child transaction.
|
105
|
+
|
106
|
+
Direct changes to returned values will not update the stored value. To update the
|
107
|
+
stored value, use the `set` method.
|
108
|
+
|
109
|
+
Args:
|
110
|
+
name: The name of the value to get
|
111
|
+
default: The default value to return if the value is not found
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
The value from the transaction
|
115
|
+
|
116
|
+
Examples:
|
117
|
+
Get a value from the transaction:
|
118
|
+
```python
|
119
|
+
with transaction() as txn:
|
120
|
+
txn.set("key", "value")
|
121
|
+
...
|
122
|
+
assert txn.get("key") == "value"
|
123
|
+
```
|
124
|
+
|
125
|
+
Get a value from a parent transaction:
|
126
|
+
```python
|
127
|
+
with transaction() as parent:
|
128
|
+
parent.set("key", "parent_value")
|
129
|
+
with transaction() as child:
|
130
|
+
assert child.get("key") == "parent_value"
|
131
|
+
```
|
132
|
+
|
133
|
+
Update a stored value:
|
134
|
+
```python
|
135
|
+
with transaction() as txn:
|
136
|
+
txn.set("key", [1, 2, 3])
|
137
|
+
value = txn.get("key")
|
138
|
+
value.append(4)
|
139
|
+
# Stored value is not updated until `.set` is called
|
140
|
+
assert value == [1, 2, 3, 4]
|
141
|
+
assert txn.get("key") == [1, 2, 3]
|
142
|
+
|
143
|
+
txn.set("key", value)
|
144
|
+
assert txn.get("key") == [1, 2, 3, 4]
|
145
|
+
```
|
146
|
+
"""
|
147
|
+
# deepcopy to prevent mutation of stored values
|
148
|
+
value = copy.deepcopy(self._stored_values.get(name, NotSet))
|
149
|
+
if value is NotSet:
|
150
|
+
# if there's a parent transaction, get the value from the parent
|
151
|
+
parent = self.get_parent()
|
152
|
+
if parent is not None:
|
153
|
+
value = parent.get(name, default)
|
154
|
+
# if there's no parent transaction, use the default
|
155
|
+
elif default is not NotSet:
|
156
|
+
value = default
|
157
|
+
else:
|
158
|
+
raise ValueError(f"Could not retrieve value for unknown key: {name}")
|
159
|
+
return value
|
86
160
|
|
87
161
|
def is_committed(self) -> bool:
|
88
162
|
return self.state == TransactionState.COMMITTED
|
@@ -105,8 +179,6 @@ class Transaction(ContextModel):
|
|
105
179
|
"Context already entered. Context enter calls cannot be nested."
|
106
180
|
)
|
107
181
|
parent = get_transaction()
|
108
|
-
if parent:
|
109
|
-
self._stored_values = copy.deepcopy(parent._stored_values)
|
110
182
|
# set default commit behavior; either inherit from parent or set a default of eager
|
111
183
|
if self.commit_mode is None:
|
112
184
|
self.commit_mode = parent.commit_mode if parent else CommitMode.LAZY
|
@@ -123,7 +195,7 @@ class Transaction(ContextModel):
|
|
123
195
|
and not self.store.supports_isolation_level(self.isolation_level)
|
124
196
|
):
|
125
197
|
raise ValueError(
|
126
|
-
f"Isolation level {self.isolation_level.name} is not supported by
|
198
|
+
f"Isolation level {self.isolation_level.name} is not supported by provided result store."
|
127
199
|
)
|
128
200
|
|
129
201
|
# this needs to go before begin, which could set the state to committed
|
@@ -177,10 +249,14 @@ class Transaction(ContextModel):
|
|
177
249
|
):
|
178
250
|
self.state = TransactionState.COMMITTED
|
179
251
|
|
180
|
-
def read(self) ->
|
252
|
+
def read(self) -> Union["BaseResult", ResultRecord, None]:
|
181
253
|
if self.store and self.key:
|
182
254
|
record = self.store.read(key=self.key)
|
183
|
-
if record
|
255
|
+
if isinstance(record, ResultRecord):
|
256
|
+
return record
|
257
|
+
# for backwards compatibility, if we encounter a transaction record, return the result
|
258
|
+
# This happens when the transaction is using a `ResultStore`
|
259
|
+
if isinstance(record, TransactionRecord):
|
184
260
|
return record.result
|
185
261
|
return None
|
186
262
|
|
@@ -229,8 +305,21 @@ class Transaction(ContextModel):
|
|
229
305
|
for hook in self.on_commit_hooks:
|
230
306
|
self.run_hook(hook, "commit")
|
231
307
|
|
232
|
-
if self.store and self.key:
|
233
|
-
self.store
|
308
|
+
if self.store and self.key and self.write_on_commit:
|
309
|
+
if isinstance(self.store, ResultStore):
|
310
|
+
if isinstance(self._staged_value, BaseResult):
|
311
|
+
self.store.write(
|
312
|
+
key=self.key, obj=self._staged_value.get(_sync=True)
|
313
|
+
)
|
314
|
+
elif isinstance(self._staged_value, ResultRecord):
|
315
|
+
self.store.persist_result_record(
|
316
|
+
result_record=self._staged_value
|
317
|
+
)
|
318
|
+
else:
|
319
|
+
self.store.write(key=self.key, obj=self._staged_value)
|
320
|
+
else:
|
321
|
+
self.store.write(key=self.key, result=self._staged_value)
|
322
|
+
|
234
323
|
self.state = TransactionState.COMMITTED
|
235
324
|
if (
|
236
325
|
self.store
|
@@ -281,7 +370,7 @@ class Transaction(ContextModel):
|
|
281
370
|
|
282
371
|
def stage(
|
283
372
|
self,
|
284
|
-
value:
|
373
|
+
value: Any,
|
285
374
|
on_rollback_hooks: Optional[List] = None,
|
286
375
|
on_commit_hooks: Optional[List] = None,
|
287
376
|
) -> None:
|
@@ -339,10 +428,11 @@ def get_transaction() -> Optional[Transaction]:
|
|
339
428
|
@contextmanager
|
340
429
|
def transaction(
|
341
430
|
key: Optional[str] = None,
|
342
|
-
store:
|
431
|
+
store: Union[RecordStore, ResultStore, None] = None,
|
343
432
|
commit_mode: Optional[CommitMode] = None,
|
344
433
|
isolation_level: Optional[IsolationLevel] = None,
|
345
434
|
overwrite: bool = False,
|
435
|
+
write_on_commit: Optional[bool] = None,
|
346
436
|
logger: Union[logging.Logger, logging.LoggerAdapter, None] = None,
|
347
437
|
) -> Generator[Transaction, None, None]:
|
348
438
|
"""
|
@@ -355,47 +445,16 @@ def transaction(
|
|
355
445
|
- commit_mode: The commit mode controlling when the transaction and
|
356
446
|
child transactions are committed
|
357
447
|
- overwrite: Whether to overwrite an existing transaction record in the store
|
448
|
+
- write_on_commit: Whether to write the result to the store on commit. If not provided,
|
449
|
+
will default will be determined by the current run context. If no run context is
|
450
|
+
available, the value of `PREFECT_RESULTS_PERSIST_BY_DEFAULT` will be used.
|
358
451
|
|
359
452
|
Yields:
|
360
453
|
- Transaction: An object representing the transaction state
|
361
454
|
"""
|
362
455
|
# if there is no key, we won't persist a record
|
363
456
|
if key and not store:
|
364
|
-
|
365
|
-
task_run_context = TaskRunContext.get()
|
366
|
-
existing_factory = getattr(task_run_context, "result_factory", None) or getattr(
|
367
|
-
flow_run_context, "result_factory", None
|
368
|
-
)
|
369
|
-
|
370
|
-
new_factory: ResultFactory
|
371
|
-
if existing_factory and existing_factory.storage_block_id:
|
372
|
-
new_factory = existing_factory.model_copy(
|
373
|
-
update={
|
374
|
-
"persist_result": True,
|
375
|
-
}
|
376
|
-
)
|
377
|
-
else:
|
378
|
-
default_storage = get_default_result_storage(_sync=True)
|
379
|
-
if existing_factory:
|
380
|
-
new_factory = existing_factory.model_copy(
|
381
|
-
update={
|
382
|
-
"persist_result": True,
|
383
|
-
"storage_block": default_storage,
|
384
|
-
"storage_block_id": default_storage._block_document_id,
|
385
|
-
}
|
386
|
-
)
|
387
|
-
else:
|
388
|
-
new_factory = run_coro_as_sync(
|
389
|
-
ResultFactory.default_factory(
|
390
|
-
persist_result=True,
|
391
|
-
result_storage=default_storage,
|
392
|
-
)
|
393
|
-
)
|
394
|
-
from prefect.records.result_store import ResultFactoryStore
|
395
|
-
|
396
|
-
store = ResultFactoryStore(
|
397
|
-
result_factory=new_factory,
|
398
|
-
)
|
457
|
+
store = get_result_store()
|
399
458
|
|
400
459
|
try:
|
401
460
|
logger = logger or get_run_logger()
|
@@ -408,6 +467,9 @@ def transaction(
|
|
408
467
|
commit_mode=commit_mode,
|
409
468
|
isolation_level=isolation_level,
|
410
469
|
overwrite=overwrite,
|
470
|
+
write_on_commit=write_on_commit
|
471
|
+
if write_on_commit is not None
|
472
|
+
else should_persist_result(),
|
411
473
|
logger=logger,
|
412
474
|
) as txn:
|
413
475
|
yield txn
|
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/engine.py
CHANGED
@@ -44,12 +44,11 @@ from prefect.exceptions import (
|
|
44
44
|
)
|
45
45
|
from prefect.flows import Flow
|
46
46
|
from prefect.futures import PrefectFuture
|
47
|
-
from prefect.futures import PrefectFuture as NewPrefectFuture
|
48
47
|
from prefect.logging.loggers import (
|
49
48
|
get_logger,
|
50
49
|
task_run_logger,
|
51
50
|
)
|
52
|
-
from prefect.results import BaseResult
|
51
|
+
from prefect.results import BaseResult, ResultRecord, should_persist_result
|
53
52
|
from prefect.settings import (
|
54
53
|
PREFECT_LOGGING_LOG_PRINTS,
|
55
54
|
)
|
@@ -122,7 +121,7 @@ async def collect_task_run_inputs(expr: Any, max_depth: int = -1) -> Set[TaskRun
|
|
122
121
|
|
123
122
|
|
124
123
|
def collect_task_run_inputs_sync(
|
125
|
-
expr: Any, future_cls: Any =
|
124
|
+
expr: Any, future_cls: Any = PrefectFuture, max_depth: int = -1
|
126
125
|
) -> Set[TaskRunInput]:
|
127
126
|
"""
|
128
127
|
This function recurses through an expression to generate a set of any discernible
|
@@ -131,7 +130,7 @@ def collect_task_run_inputs_sync(
|
|
131
130
|
|
132
131
|
Examples:
|
133
132
|
>>> task_inputs = {
|
134
|
-
>>> k:
|
133
|
+
>>> k: collect_task_run_inputs_sync(v) for k, v in parameters.items()
|
135
134
|
>>> }
|
136
135
|
"""
|
137
136
|
# TODO: This function needs to be updated to detect parameters and constants
|
@@ -401,6 +400,8 @@ async def propose_state(
|
|
401
400
|
# Avoid fetching the result unless it is cached, otherwise we defeat
|
402
401
|
# the purpose of disabling `cache_result_in_memory`
|
403
402
|
result = await state.result(raise_on_failure=False, fetch=True)
|
403
|
+
elif isinstance(state.data, ResultRecord):
|
404
|
+
result = state.data.result
|
404
405
|
else:
|
405
406
|
result = state.data
|
406
407
|
|
@@ -504,6 +505,8 @@ def propose_state_sync(
|
|
504
505
|
result = state.result(raise_on_failure=False, fetch=True)
|
505
506
|
if inspect.isawaitable(result):
|
506
507
|
result = run_coro_as_sync(result)
|
508
|
+
elif isinstance(state.data, ResultRecord):
|
509
|
+
result = state.data.result
|
507
510
|
else:
|
508
511
|
result = state.data
|
509
512
|
|
@@ -732,6 +735,13 @@ def emit_task_run_state_change_event(
|
|
732
735
|
) -> Event:
|
733
736
|
state_message_truncation_length = 100_000
|
734
737
|
|
738
|
+
if isinstance(validated_state.data, ResultRecord) and should_persist_result():
|
739
|
+
data = validated_state.data.metadata.model_dump(mode="json")
|
740
|
+
elif isinstance(validated_state.data, BaseResult):
|
741
|
+
data = validated_state.data.model_dump(mode="json")
|
742
|
+
else:
|
743
|
+
data = None
|
744
|
+
|
735
745
|
return emit_event(
|
736
746
|
id=validated_state.id,
|
737
747
|
occurred=validated_state.timestamp,
|
@@ -770,9 +780,7 @@ def emit_task_run_state_change_event(
|
|
770
780
|
exclude_unset=True,
|
771
781
|
exclude={"flow_run_id", "task_run_id"},
|
772
782
|
),
|
773
|
-
"data":
|
774
|
-
if isinstance(validated_state.data, BaseResult)
|
775
|
-
else None,
|
783
|
+
"data": data,
|
776
784
|
},
|
777
785
|
"task_run": task_run.model_dump(
|
778
786
|
mode="json",
|
@@ -822,7 +830,7 @@ def resolve_to_final_result(expr, context):
|
|
822
830
|
if isinstance(context.get("annotation"), quote):
|
823
831
|
raise StopVisiting()
|
824
832
|
|
825
|
-
if isinstance(expr,
|
833
|
+
if isinstance(expr, PrefectFuture):
|
826
834
|
upstream_task_run = context.get("current_task_run")
|
827
835
|
upstream_task = context.get("current_task")
|
828
836
|
if (
|
prefect/utilities/importtools.py
CHANGED
@@ -398,6 +398,7 @@ def safe_load_namespace(
|
|
398
398
|
# Save original sys.path and modify it
|
399
399
|
original_sys_path = sys.path.copy()
|
400
400
|
sys.path.insert(0, parent_dir)
|
401
|
+
sys.path.insert(0, file_dir)
|
401
402
|
|
402
403
|
# Create a temporary module for import context
|
403
404
|
temp_module = ModuleType(package_name)
|
prefect/utilities/urls.py
CHANGED
@@ -1,17 +1,22 @@
|
|
1
1
|
import inspect
|
2
|
+
import ipaddress
|
3
|
+
import socket
|
2
4
|
import urllib.parse
|
3
|
-
from typing import Any, Literal, Optional, Union
|
5
|
+
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
6
|
+
from urllib.parse import urlparse
|
4
7
|
from uuid import UUID
|
5
8
|
|
6
9
|
from pydantic import BaseModel
|
7
10
|
|
8
11
|
from prefect import settings
|
9
|
-
from prefect.blocks.core import Block
|
10
|
-
from prefect.events.schemas.automations import Automation
|
11
|
-
from prefect.events.schemas.events import ReceivedEvent, Resource
|
12
|
-
from prefect.futures import PrefectFuture
|
13
12
|
from prefect.logging.loggers import get_logger
|
14
|
-
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from prefect.blocks.core import Block
|
16
|
+
from prefect.events.schemas.automations import Automation
|
17
|
+
from prefect.events.schemas.events import ReceivedEvent, Resource
|
18
|
+
from prefect.futures import PrefectFuture
|
19
|
+
from prefect.variables import Variable
|
15
20
|
|
16
21
|
logger = get_logger("utilities.urls")
|
17
22
|
|
@@ -58,6 +63,54 @@ URLType = Literal["ui", "api"]
|
|
58
63
|
RUN_TYPES = {"flow-run", "task-run"}
|
59
64
|
|
60
65
|
|
66
|
+
def validate_restricted_url(url: str):
|
67
|
+
"""
|
68
|
+
Validate that the provided URL is safe for outbound requests. This prevents
|
69
|
+
attacks like SSRF (Server Side Request Forgery), where an attacker can make
|
70
|
+
requests to internal services (like the GCP metadata service, localhost addresses,
|
71
|
+
or in-cluster Kubernetes services)
|
72
|
+
|
73
|
+
Args:
|
74
|
+
url: The URL to validate.
|
75
|
+
|
76
|
+
Raises:
|
77
|
+
ValueError: If the URL is a restricted URL.
|
78
|
+
"""
|
79
|
+
|
80
|
+
try:
|
81
|
+
parsed_url = urlparse(url)
|
82
|
+
except ValueError:
|
83
|
+
raise ValueError(f"{url!r} is not a valid URL.")
|
84
|
+
|
85
|
+
if parsed_url.scheme not in ("http", "https"):
|
86
|
+
raise ValueError(
|
87
|
+
f"{url!r} is not a valid URL. Only HTTP and HTTPS URLs are allowed."
|
88
|
+
)
|
89
|
+
|
90
|
+
hostname = parsed_url.hostname or ""
|
91
|
+
|
92
|
+
# Remove IPv6 brackets if present
|
93
|
+
if hostname.startswith("[") and hostname.endswith("]"):
|
94
|
+
hostname = hostname[1:-1]
|
95
|
+
|
96
|
+
if not hostname:
|
97
|
+
raise ValueError(f"{url!r} is not a valid URL.")
|
98
|
+
|
99
|
+
try:
|
100
|
+
ip_address = socket.gethostbyname(hostname)
|
101
|
+
ip = ipaddress.ip_address(ip_address)
|
102
|
+
except socket.gaierror:
|
103
|
+
try:
|
104
|
+
ip = ipaddress.ip_address(hostname)
|
105
|
+
except ValueError:
|
106
|
+
raise ValueError(f"{url!r} is not a valid URL. It could not be resolved.")
|
107
|
+
|
108
|
+
if ip.is_private:
|
109
|
+
raise ValueError(
|
110
|
+
f"{url!r} is not a valid URL. It resolves to the private address {ip}."
|
111
|
+
)
|
112
|
+
|
113
|
+
|
61
114
|
def convert_class_to_name(obj: Any) -> str:
|
62
115
|
"""
|
63
116
|
Convert CamelCase class name to dash-separated lowercase name
|
@@ -69,12 +122,12 @@ def convert_class_to_name(obj: Any) -> str:
|
|
69
122
|
|
70
123
|
def url_for(
|
71
124
|
obj: Union[
|
72
|
-
PrefectFuture,
|
73
|
-
Block,
|
74
|
-
Variable,
|
75
|
-
Automation,
|
76
|
-
Resource,
|
77
|
-
ReceivedEvent,
|
125
|
+
"PrefectFuture",
|
126
|
+
"Block",
|
127
|
+
"Variable",
|
128
|
+
"Automation",
|
129
|
+
"Resource",
|
130
|
+
"ReceivedEvent",
|
78
131
|
BaseModel,
|
79
132
|
str,
|
80
133
|
],
|
@@ -105,6 +158,11 @@ def url_for(
|
|
105
158
|
url_for(obj=my_flow_run)
|
106
159
|
url_for("flow-run", obj_id="123e4567-e89b-12d3-a456-426614174000")
|
107
160
|
"""
|
161
|
+
from prefect.blocks.core import Block
|
162
|
+
from prefect.events.schemas.automations import Automation
|
163
|
+
from prefect.events.schemas.events import ReceivedEvent, Resource
|
164
|
+
from prefect.futures import PrefectFuture
|
165
|
+
|
108
166
|
if isinstance(obj, PrefectFuture):
|
109
167
|
name = "task-run"
|
110
168
|
elif isinstance(obj, Block):
|
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
@@ -146,6 +146,12 @@ class BaseJobConfiguration(BaseModel):
|
|
146
146
|
)
|
147
147
|
variables.update(values)
|
148
148
|
|
149
|
+
# deep merge `env`
|
150
|
+
if isinstance(job_config.get("env"), dict) and (
|
151
|
+
hardcoded_env := variables.get("env")
|
152
|
+
):
|
153
|
+
job_config["env"] = hardcoded_env | job_config.get("env")
|
154
|
+
|
149
155
|
populated_configuration = apply_values(template=job_config, values=variables)
|
150
156
|
populated_configuration = await resolve_block_document_references(
|
151
157
|
template=populated_configuration, client=client
|
@@ -865,17 +871,19 @@ class BaseWorker(abc.ABC):
|
|
865
871
|
deployment = await self._client.read_deployment(flow_run.deployment_id)
|
866
872
|
if deployment and deployment.concurrency_limit:
|
867
873
|
limit_name = f"deployment:{deployment.id}"
|
868
|
-
concurrency_limit = deployment.concurrency_limit
|
869
874
|
concurrency_ctx = concurrency
|
875
|
+
|
876
|
+
# ensure that the global concurrency limit is available
|
877
|
+
# and up-to-date before attempting to acquire a slot
|
878
|
+
await self._client.upsert_global_concurrency_limit_by_name(
|
879
|
+
limit_name, deployment.concurrency_limit
|
880
|
+
)
|
870
881
|
else:
|
871
|
-
limit_name =
|
872
|
-
concurrency_limit = None
|
882
|
+
limit_name = ""
|
873
883
|
concurrency_ctx = asyncnullcontext
|
874
884
|
|
875
885
|
try:
|
876
|
-
async with concurrency_ctx(
|
877
|
-
limit_name, occupy=concurrency_limit, max_retries=0
|
878
|
-
):
|
886
|
+
async with concurrency_ctx(limit_name, max_retries=0, strict=True):
|
879
887
|
configuration = await self._get_configuration(flow_run, deployment)
|
880
888
|
submitted_event = self._emit_flow_run_submitted_event(configuration)
|
881
889
|
result = await self.run(
|
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.2
|
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
|