prefect-client 3.1.5__py3-none-any.whl → 3.1.7__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 +3 -0
- prefect/_experimental/__init__.py +0 -0
- prefect/_experimental/lineage.py +181 -0
- prefect/_internal/compatibility/async_dispatch.py +38 -9
- prefect/_internal/compatibility/migration.py +1 -1
- prefect/_internal/concurrency/api.py +52 -52
- prefect/_internal/concurrency/calls.py +59 -35
- prefect/_internal/concurrency/cancellation.py +34 -18
- prefect/_internal/concurrency/event_loop.py +7 -6
- prefect/_internal/concurrency/threads.py +41 -33
- prefect/_internal/concurrency/waiters.py +28 -21
- prefect/_internal/pydantic/v1_schema.py +2 -2
- prefect/_internal/pydantic/v2_schema.py +10 -9
- prefect/_internal/pydantic/v2_validated_func.py +15 -10
- prefect/_internal/retries.py +15 -6
- prefect/_internal/schemas/bases.py +11 -8
- prefect/_internal/schemas/validators.py +7 -5
- prefect/_version.py +3 -3
- prefect/automations.py +53 -47
- prefect/blocks/abstract.py +12 -10
- prefect/blocks/core.py +148 -19
- prefect/blocks/system.py +2 -1
- prefect/cache_policies.py +11 -11
- prefect/client/__init__.py +3 -1
- prefect/client/base.py +36 -37
- prefect/client/cloud.py +26 -19
- prefect/client/collections.py +2 -2
- prefect/client/orchestration.py +430 -273
- prefect/client/schemas/__init__.py +24 -0
- prefect/client/schemas/actions.py +128 -121
- prefect/client/schemas/filters.py +1 -1
- prefect/client/schemas/objects.py +114 -85
- prefect/client/schemas/responses.py +19 -20
- prefect/client/schemas/schedules.py +136 -93
- prefect/client/subscriptions.py +30 -15
- prefect/client/utilities.py +46 -36
- prefect/concurrency/asyncio.py +6 -9
- prefect/concurrency/sync.py +35 -5
- prefect/context.py +40 -32
- prefect/deployments/flow_runs.py +6 -8
- prefect/deployments/runner.py +14 -14
- prefect/deployments/steps/core.py +3 -1
- prefect/deployments/steps/pull.py +60 -12
- prefect/docker/__init__.py +1 -1
- prefect/events/clients.py +55 -4
- prefect/events/filters.py +1 -1
- prefect/events/related.py +2 -1
- prefect/events/schemas/events.py +26 -21
- prefect/events/utilities.py +3 -2
- prefect/events/worker.py +8 -0
- prefect/filesystems.py +3 -3
- prefect/flow_engine.py +87 -87
- prefect/flow_runs.py +7 -5
- prefect/flows.py +218 -176
- prefect/logging/configuration.py +1 -1
- prefect/logging/highlighters.py +1 -2
- prefect/logging/loggers.py +30 -20
- prefect/main.py +17 -24
- prefect/results.py +43 -22
- prefect/runner/runner.py +43 -21
- prefect/runner/server.py +30 -32
- prefect/runner/storage.py +3 -3
- prefect/runner/submit.py +3 -6
- prefect/runner/utils.py +6 -6
- prefect/runtime/flow_run.py +7 -0
- prefect/serializers.py +28 -24
- prefect/settings/constants.py +2 -2
- prefect/settings/legacy.py +1 -1
- prefect/settings/models/experiments.py +5 -0
- prefect/settings/models/server/events.py +10 -0
- prefect/task_engine.py +87 -26
- prefect/task_runners.py +2 -2
- prefect/task_worker.py +43 -25
- prefect/tasks.py +148 -142
- prefect/telemetry/bootstrap.py +15 -2
- prefect/telemetry/instrumentation.py +1 -1
- prefect/telemetry/processors.py +10 -7
- prefect/telemetry/run_telemetry.py +231 -0
- prefect/transactions.py +14 -14
- prefect/types/__init__.py +5 -5
- prefect/utilities/_engine.py +96 -0
- prefect/utilities/annotations.py +25 -18
- prefect/utilities/asyncutils.py +126 -140
- prefect/utilities/callables.py +87 -78
- prefect/utilities/collections.py +278 -117
- prefect/utilities/compat.py +13 -21
- prefect/utilities/context.py +6 -5
- prefect/utilities/dispatch.py +23 -12
- prefect/utilities/dockerutils.py +33 -32
- prefect/utilities/engine.py +126 -239
- prefect/utilities/filesystem.py +18 -15
- prefect/utilities/hashing.py +10 -11
- prefect/utilities/importtools.py +40 -27
- prefect/utilities/math.py +9 -5
- prefect/utilities/names.py +3 -3
- prefect/utilities/processutils.py +121 -57
- prefect/utilities/pydantic.py +41 -36
- prefect/utilities/render_swagger.py +22 -12
- prefect/utilities/schema_tools/__init__.py +2 -1
- prefect/utilities/schema_tools/hydration.py +50 -43
- prefect/utilities/schema_tools/validation.py +52 -42
- prefect/utilities/services.py +13 -12
- prefect/utilities/templating.py +45 -45
- prefect/utilities/text.py +2 -1
- prefect/utilities/timeout.py +4 -4
- prefect/utilities/urls.py +9 -4
- prefect/utilities/visualization.py +46 -24
- prefect/variables.py +136 -27
- prefect/workers/base.py +15 -8
- {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/METADATA +5 -2
- {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/RECORD +114 -110
- {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/LICENSE +0 -0
- {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/WHEEL +0 -0
- {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/top_level.txt +0 -0
@@ -13,13 +13,14 @@ import warnings
|
|
13
13
|
from copy import copy
|
14
14
|
from pathlib import Path
|
15
15
|
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple, Union
|
16
|
+
from uuid import UUID
|
16
17
|
|
17
18
|
import jsonschema
|
18
19
|
import pendulum
|
19
20
|
import yaml
|
20
|
-
from pydantic_extra_types.pendulum_dt import DateTime
|
21
21
|
|
22
22
|
from prefect.exceptions import InvalidRepositoryURLError
|
23
|
+
from prefect.types import DateTime
|
23
24
|
from prefect.utilities.collections import isiterable
|
24
25
|
from prefect.utilities.dockerutils import get_prefect_image_name
|
25
26
|
from prefect.utilities.filesystem import relative_path_to_current_platform
|
@@ -32,6 +33,7 @@ LOWERCASE_LETTERS_NUMBERS_AND_UNDERSCORES_REGEX = "^[a-z0-9_]*$"
|
|
32
33
|
|
33
34
|
if TYPE_CHECKING:
|
34
35
|
from prefect.blocks.core import Block
|
36
|
+
from prefect.serializers import Serializer
|
35
37
|
from prefect.utilities.callables import ParameterSchema
|
36
38
|
|
37
39
|
|
@@ -577,7 +579,7 @@ def validate_picklelib_and_modules(values: dict) -> dict:
|
|
577
579
|
return values
|
578
580
|
|
579
581
|
|
580
|
-
def validate_dump_kwargs(value: dict) -> dict:
|
582
|
+
def validate_dump_kwargs(value: dict[str, Any]) -> dict[str, Any]:
|
581
583
|
# `default` is set by `object_encoder`. A user provided callable would make this
|
582
584
|
# class unserializable anyway.
|
583
585
|
if "default" in value:
|
@@ -585,7 +587,7 @@ def validate_dump_kwargs(value: dict) -> dict:
|
|
585
587
|
return value
|
586
588
|
|
587
589
|
|
588
|
-
def validate_load_kwargs(value: dict) -> dict:
|
590
|
+
def validate_load_kwargs(value: dict[str, Any]) -> dict[str, Any]:
|
589
591
|
# `object_hook` is set by `object_decoder`. A user provided callable would make
|
590
592
|
# this class unserializable anyway.
|
591
593
|
if "object_hook" in value:
|
@@ -595,7 +597,7 @@ def validate_load_kwargs(value: dict) -> dict:
|
|
595
597
|
return value
|
596
598
|
|
597
599
|
|
598
|
-
def cast_type_names_to_serializers(value):
|
600
|
+
def cast_type_names_to_serializers(value: Union[str, "Serializer"]) -> "Serializer":
|
599
601
|
from prefect.serializers import Serializer
|
600
602
|
|
601
603
|
if isinstance(value, str):
|
@@ -653,7 +655,7 @@ def validate_message_template_variables(v: Optional[str]) -> Optional[str]:
|
|
653
655
|
return v
|
654
656
|
|
655
657
|
|
656
|
-
def validate_default_queue_id_not_none(v: Optional[
|
658
|
+
def validate_default_queue_id_not_none(v: Optional[UUID]) -> UUID:
|
657
659
|
if v is None:
|
658
660
|
raise ValueError(
|
659
661
|
"`default_queue_id` is a required field. If you are "
|
prefect/_version.py
CHANGED
@@ -8,11 +8,11 @@ import json
|
|
8
8
|
|
9
9
|
version_json = '''
|
10
10
|
{
|
11
|
-
"date": "2024-12-
|
11
|
+
"date": "2024-12-16T10:06:12-0800",
|
12
12
|
"dirty": true,
|
13
13
|
"error": null,
|
14
|
-
"full-revisionid": "
|
15
|
-
"version": "3.1.
|
14
|
+
"full-revisionid": "c05ffa6d1816f980d574b42119dffb9f8c8300a3",
|
15
|
+
"version": "3.1.7"
|
16
16
|
}
|
17
17
|
''' # END VERSION_JSON
|
18
18
|
|
prefect/automations.py
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
from typing import Optional
|
1
|
+
from typing import Optional, Type
|
2
2
|
from uuid import UUID
|
3
3
|
|
4
4
|
from pydantic import Field
|
5
5
|
from typing_extensions import Self
|
6
6
|
|
7
|
-
from prefect.client.
|
7
|
+
from prefect.client.orchestration import get_client
|
8
8
|
from prefect.events.actions import (
|
9
9
|
CallWebhook,
|
10
10
|
CancelFlowRun,
|
@@ -99,10 +99,10 @@ class Automation(AutomationCore):
|
|
99
99
|
)
|
100
100
|
created_automation = auto_to_create.create()
|
101
101
|
"""
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
102
|
+
async with get_client() as client:
|
103
|
+
automation = AutomationCore(**self.model_dump(exclude={"id"}))
|
104
|
+
self.id = await client.create_automation(automation=automation)
|
105
|
+
return self
|
106
106
|
|
107
107
|
@sync_compatible
|
108
108
|
async def update(self: Self):
|
@@ -112,15 +112,16 @@ class Automation(AutomationCore):
|
|
112
112
|
auto.name = "new name"
|
113
113
|
auto.update()
|
114
114
|
"""
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
115
|
+
async with get_client() as client:
|
116
|
+
automation = AutomationCore(
|
117
|
+
**self.model_dump(exclude={"id", "owner_resource"})
|
118
|
+
)
|
119
|
+
await client.update_automation(automation_id=self.id, automation=automation)
|
119
120
|
|
120
121
|
@classmethod
|
121
122
|
@sync_compatible
|
122
123
|
async def read(
|
123
|
-
cls: Self, id: Optional[UUID] = None, name: Optional[str] = None
|
124
|
+
cls: Type[Self], id: Optional[UUID] = None, name: Optional[str] = None
|
124
125
|
) -> Self:
|
125
126
|
"""
|
126
127
|
Read an automation by ID or name.
|
@@ -134,20 +135,25 @@ class Automation(AutomationCore):
|
|
134
135
|
raise ValueError("Only one of id or name can be provided")
|
135
136
|
if not id and not name:
|
136
137
|
raise ValueError("One of id or name must be provided")
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
138
|
+
async with get_client() as client:
|
139
|
+
if id:
|
140
|
+
try:
|
141
|
+
automation = await client.read_automation(automation_id=id)
|
142
|
+
except PrefectHTTPStatusError as exc:
|
143
|
+
if exc.response.status_code == 404:
|
144
|
+
raise ValueError(f"Automation with ID {id!r} not found")
|
145
|
+
raise
|
146
|
+
if automation is None:
|
143
147
|
raise ValueError(f"Automation with ID {id!r} not found")
|
144
|
-
|
145
|
-
else:
|
146
|
-
automation = await client.read_automations_by_name(name=name)
|
147
|
-
if len(automation) > 0:
|
148
|
-
return Automation(**automation[0].model_dump()) if automation else None
|
148
|
+
return Automation(**automation.model_dump())
|
149
149
|
else:
|
150
|
-
|
150
|
+
automation = await client.read_automations_by_name(name=name)
|
151
|
+
if len(automation) > 0:
|
152
|
+
return (
|
153
|
+
Automation(**automation[0].model_dump()) if automation else None
|
154
|
+
)
|
155
|
+
else:
|
156
|
+
raise ValueError(f"Automation with name {name!r} not found")
|
151
157
|
|
152
158
|
@sync_compatible
|
153
159
|
async def delete(self: Self) -> bool:
|
@@ -155,14 +161,14 @@ class Automation(AutomationCore):
|
|
155
161
|
auto = Automation.read(id = 123)
|
156
162
|
auto.delete()
|
157
163
|
"""
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
164
|
+
async with get_client() as client:
|
165
|
+
try:
|
166
|
+
await client.delete_automation(self.id)
|
167
|
+
return True
|
168
|
+
except PrefectHTTPStatusError as exc:
|
169
|
+
if exc.response.status_code == 404:
|
170
|
+
return False
|
171
|
+
raise
|
166
172
|
|
167
173
|
@sync_compatible
|
168
174
|
async def disable(self: Self) -> bool:
|
@@ -171,14 +177,14 @@ class Automation(AutomationCore):
|
|
171
177
|
auto = Automation.read(id = 123)
|
172
178
|
auto.disable()
|
173
179
|
"""
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
180
|
+
async with get_client() as client:
|
181
|
+
try:
|
182
|
+
await client.pause_automation(self.id)
|
183
|
+
return True
|
184
|
+
except PrefectHTTPStatusError as exc:
|
185
|
+
if exc.response.status_code == 404:
|
186
|
+
return False
|
187
|
+
raise
|
182
188
|
|
183
189
|
@sync_compatible
|
184
190
|
async def enable(self: Self) -> bool:
|
@@ -187,11 +193,11 @@ class Automation(AutomationCore):
|
|
187
193
|
auto = Automation.read(id = 123)
|
188
194
|
auto.enable()
|
189
195
|
"""
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
196
|
+
async with get_client() as client:
|
197
|
+
try:
|
198
|
+
await client.resume_automation(self.id)
|
199
|
+
return True
|
200
|
+
except PrefectHTTPStatusError as exc:
|
201
|
+
if exc.response.status_code == 404:
|
202
|
+
return False
|
203
|
+
raise
|
prefect/blocks/abstract.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
2
|
from contextlib import contextmanager
|
3
|
-
from logging import Logger
|
3
|
+
from logging import Logger, LoggerAdapter
|
4
4
|
from pathlib import Path
|
5
5
|
from typing import (
|
6
6
|
Any,
|
@@ -15,7 +15,7 @@ from typing import (
|
|
15
15
|
Union,
|
16
16
|
)
|
17
17
|
|
18
|
-
from typing_extensions import Self
|
18
|
+
from typing_extensions import Self, TypeAlias
|
19
19
|
|
20
20
|
from prefect.blocks.core import Block
|
21
21
|
from prefect.exceptions import MissingContextError
|
@@ -23,6 +23,8 @@ from prefect.logging.loggers import get_logger, get_run_logger
|
|
23
23
|
|
24
24
|
T = TypeVar("T")
|
25
25
|
|
26
|
+
LoggerOrAdapter: TypeAlias = Union[Logger, LoggerAdapter]
|
27
|
+
|
26
28
|
|
27
29
|
class CredentialsBlock(Block, ABC):
|
28
30
|
"""
|
@@ -34,7 +36,7 @@ class CredentialsBlock(Block, ABC):
|
|
34
36
|
"""
|
35
37
|
|
36
38
|
@property
|
37
|
-
def logger(self) ->
|
39
|
+
def logger(self) -> LoggerOrAdapter:
|
38
40
|
"""
|
39
41
|
Returns a logger based on whether the CredentialsBlock
|
40
42
|
is called from within a flow or task run context.
|
@@ -73,10 +75,10 @@ class NotificationBlock(Block, ABC):
|
|
73
75
|
"""
|
74
76
|
|
75
77
|
_block_schema_capabilities = ["notify"]
|
76
|
-
_events_excluded_methods = Block._events_excluded_methods
|
78
|
+
_events_excluded_methods = Block._events_excluded_methods + ["notify"]
|
77
79
|
|
78
80
|
@property
|
79
|
-
def logger(self) ->
|
81
|
+
def logger(self) -> LoggerOrAdapter:
|
80
82
|
"""
|
81
83
|
Returns a logger based on whether the NotificationBlock
|
82
84
|
is called from within a flow or task run context.
|
@@ -123,7 +125,7 @@ class JobRun(ABC, Generic[T]): # not a block
|
|
123
125
|
"""
|
124
126
|
|
125
127
|
@property
|
126
|
-
def logger(self) ->
|
128
|
+
def logger(self) -> LoggerOrAdapter:
|
127
129
|
"""
|
128
130
|
Returns a logger based on whether the JobRun
|
129
131
|
is called from within a flow or task run context.
|
@@ -158,7 +160,7 @@ class JobBlock(Block, ABC):
|
|
158
160
|
"""
|
159
161
|
|
160
162
|
@property
|
161
|
-
def logger(self) ->
|
163
|
+
def logger(self) -> LoggerOrAdapter:
|
162
164
|
"""
|
163
165
|
Returns a logger based on whether the JobBlock
|
164
166
|
is called from within a flow or task run context.
|
@@ -202,7 +204,7 @@ class DatabaseBlock(Block, ABC):
|
|
202
204
|
"""
|
203
205
|
|
204
206
|
@property
|
205
|
-
def logger(self) ->
|
207
|
+
def logger(self) -> LoggerOrAdapter:
|
206
208
|
"""
|
207
209
|
Returns a logger based on whether the DatabaseBlock
|
208
210
|
is called from within a flow or task run context.
|
@@ -337,7 +339,7 @@ class ObjectStorageBlock(Block, ABC):
|
|
337
339
|
"""
|
338
340
|
|
339
341
|
@property
|
340
|
-
def logger(self) ->
|
342
|
+
def logger(self) -> LoggerOrAdapter:
|
341
343
|
"""
|
342
344
|
Returns a logger based on whether the ObjectStorageBlock
|
343
345
|
is called from within a flow or task run context.
|
@@ -469,7 +471,7 @@ class SecretBlock(Block, ABC):
|
|
469
471
|
"""
|
470
472
|
|
471
473
|
@property
|
472
|
-
def logger(self) ->
|
474
|
+
def logger(self) -> LoggerOrAdapter:
|
473
475
|
"""
|
474
476
|
Returns a logger based on whether the SecretBlock
|
475
477
|
is called from within a flow or task run context.
|
prefect/blocks/core.py
CHANGED
@@ -41,6 +41,7 @@ from pydantic.json_schema import GenerateJsonSchema
|
|
41
41
|
from typing_extensions import Literal, ParamSpec, Self, get_args
|
42
42
|
|
43
43
|
import prefect.exceptions
|
44
|
+
from prefect._internal.compatibility.async_dispatch import async_dispatch
|
44
45
|
from prefect.client.schemas import (
|
45
46
|
DEFAULT_BLOCK_SCHEMA_VERSION,
|
46
47
|
BlockDocument,
|
@@ -53,7 +54,7 @@ from prefect.events import emit_event
|
|
53
54
|
from prefect.logging.loggers import disable_logger
|
54
55
|
from prefect.plugins import load_prefect_collections
|
55
56
|
from prefect.types import SecretDict
|
56
|
-
from prefect.utilities.asyncutils import sync_compatible
|
57
|
+
from prefect.utilities.asyncutils import run_coro_as_sync, sync_compatible
|
57
58
|
from prefect.utilities.collections import listrepr, remove_nested_keys, visit_collection
|
58
59
|
from prefect.utilities.dispatch import lookup_type, register_base_type
|
59
60
|
from prefect.utilities.hashing import hash_objects
|
@@ -64,7 +65,7 @@ from prefect.utilities.slugify import slugify
|
|
64
65
|
if TYPE_CHECKING:
|
65
66
|
from pydantic.main import IncEx
|
66
67
|
|
67
|
-
from prefect.client.orchestration import PrefectClient
|
68
|
+
from prefect.client.orchestration import PrefectClient, SyncPrefectClient
|
68
69
|
|
69
70
|
R = TypeVar("R")
|
70
71
|
P = ParamSpec("P")
|
@@ -280,7 +281,7 @@ class Block(BaseModel, ABC):
|
|
280
281
|
json_schema_extra=schema_extra,
|
281
282
|
)
|
282
283
|
|
283
|
-
def __init__(self, *args, **kwargs):
|
284
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
284
285
|
super().__init__(*args, **kwargs)
|
285
286
|
self.block_initialization()
|
286
287
|
|
@@ -326,7 +327,9 @@ class Block(BaseModel, ABC):
|
|
326
327
|
|
327
328
|
# Exclude `save` as it uses the `sync_compatible` decorator and needs to be
|
328
329
|
# decorated directly.
|
329
|
-
_events_excluded_methods
|
330
|
+
_events_excluded_methods: ClassVar[List[str]] = PrivateAttr(
|
331
|
+
default=["block_initialization", "save", "dict"]
|
332
|
+
)
|
330
333
|
|
331
334
|
@classmethod
|
332
335
|
def __dispatch_key__(cls):
|
@@ -626,7 +629,8 @@ class Block(BaseModel, ABC):
|
|
626
629
|
"""Generates a default code example for the current class"""
|
627
630
|
qualified_name = to_qualified_name(cls)
|
628
631
|
module_str = ".".join(qualified_name.split(".")[:-1])
|
629
|
-
|
632
|
+
origin = cls.__pydantic_generic_metadata__.get("origin") or cls
|
633
|
+
class_name = origin.__name__
|
630
634
|
block_variable_name = f'{cls.get_block_type_slug().replace("-", "_")}_block'
|
631
635
|
|
632
636
|
return dedent(
|
@@ -775,12 +779,11 @@ class Block(BaseModel, ABC):
|
|
775
779
|
)
|
776
780
|
|
777
781
|
@classmethod
|
778
|
-
|
779
|
-
async def _get_block_document(
|
782
|
+
async def _aget_block_document(
|
780
783
|
cls,
|
781
784
|
name: str,
|
782
|
-
client:
|
783
|
-
):
|
785
|
+
client: "PrefectClient",
|
786
|
+
) -> tuple[BlockDocument, str]:
|
784
787
|
if cls.__name__ == "Block":
|
785
788
|
block_type_slug, block_document_name = name.split("/", 1)
|
786
789
|
else:
|
@@ -799,6 +802,30 @@ class Block(BaseModel, ABC):
|
|
799
802
|
|
800
803
|
return block_document, block_document_name
|
801
804
|
|
805
|
+
@classmethod
|
806
|
+
def _get_block_document(
|
807
|
+
cls,
|
808
|
+
name: str,
|
809
|
+
client: "SyncPrefectClient",
|
810
|
+
) -> tuple[BlockDocument, str]:
|
811
|
+
if cls.__name__ == "Block":
|
812
|
+
block_type_slug, block_document_name = name.split("/", 1)
|
813
|
+
else:
|
814
|
+
block_type_slug = cls.get_block_type_slug()
|
815
|
+
block_document_name = name
|
816
|
+
|
817
|
+
try:
|
818
|
+
block_document = client.read_block_document_by_name(
|
819
|
+
name=block_document_name, block_type_slug=block_type_slug
|
820
|
+
)
|
821
|
+
except prefect.exceptions.ObjectNotFound as e:
|
822
|
+
raise ValueError(
|
823
|
+
f"Unable to find block document named {block_document_name} for block"
|
824
|
+
f" type {block_type_slug}"
|
825
|
+
) from e
|
826
|
+
|
827
|
+
return block_document, block_document_name
|
828
|
+
|
802
829
|
@classmethod
|
803
830
|
@sync_compatible
|
804
831
|
@inject_client
|
@@ -827,9 +854,97 @@ class Block(BaseModel, ABC):
|
|
827
854
|
return block_document, block_document.name
|
828
855
|
|
829
856
|
@classmethod
|
830
|
-
@sync_compatible
|
831
857
|
@inject_client
|
832
|
-
async def
|
858
|
+
async def aload(
|
859
|
+
cls,
|
860
|
+
name: str,
|
861
|
+
validate: bool = True,
|
862
|
+
client: Optional["PrefectClient"] = None,
|
863
|
+
) -> "Self":
|
864
|
+
"""
|
865
|
+
Retrieves data from the block document with the given name for the block type
|
866
|
+
that corresponds with the current class and returns an instantiated version of
|
867
|
+
the current class with the data stored in the block document.
|
868
|
+
|
869
|
+
If a block document for a given block type is saved with a different schema
|
870
|
+
than the current class calling `aload`, a warning will be raised.
|
871
|
+
|
872
|
+
If the current class schema is a subset of the block document schema, the block
|
873
|
+
can be loaded as normal using the default `validate = True`.
|
874
|
+
|
875
|
+
If the current class schema is a superset of the block document schema, `aload`
|
876
|
+
must be called with `validate` set to False to prevent a validation error. In
|
877
|
+
this case, the block attributes will default to `None` and must be set manually
|
878
|
+
and saved to a new block document before the block can be used as expected.
|
879
|
+
|
880
|
+
Args:
|
881
|
+
name: The name or slug of the block document. A block document slug is a
|
882
|
+
string with the format <block_type_slug>/<block_document_name>
|
883
|
+
validate: If False, the block document will be loaded without Pydantic
|
884
|
+
validating the block schema. This is useful if the block schema has
|
885
|
+
changed client-side since the block document referred to by `name` was saved.
|
886
|
+
client: The client to use to load the block document. If not provided, the
|
887
|
+
default client will be injected.
|
888
|
+
|
889
|
+
Raises:
|
890
|
+
ValueError: If the requested block document is not found.
|
891
|
+
|
892
|
+
Returns:
|
893
|
+
An instance of the current class hydrated with the data stored in the
|
894
|
+
block document with the specified name.
|
895
|
+
|
896
|
+
Examples:
|
897
|
+
Load from a Block subclass with a block document name:
|
898
|
+
```python
|
899
|
+
class Custom(Block):
|
900
|
+
message: str
|
901
|
+
|
902
|
+
Custom(message="Hello!").save("my-custom-message")
|
903
|
+
|
904
|
+
loaded_block = await Custom.aload("my-custom-message")
|
905
|
+
```
|
906
|
+
|
907
|
+
Load from Block with a block document slug:
|
908
|
+
```python
|
909
|
+
class Custom(Block):
|
910
|
+
message: str
|
911
|
+
|
912
|
+
Custom(message="Hello!").save("my-custom-message")
|
913
|
+
|
914
|
+
loaded_block = await Block.aload("custom/my-custom-message")
|
915
|
+
```
|
916
|
+
|
917
|
+
Migrate a block document to a new schema:
|
918
|
+
```python
|
919
|
+
# original class
|
920
|
+
class Custom(Block):
|
921
|
+
message: str
|
922
|
+
|
923
|
+
Custom(message="Hello!").save("my-custom-message")
|
924
|
+
|
925
|
+
# Updated class with new required field
|
926
|
+
class Custom(Block):
|
927
|
+
message: str
|
928
|
+
number_of_ducks: int
|
929
|
+
|
930
|
+
loaded_block = await Custom.aload("my-custom-message", validate=False)
|
931
|
+
|
932
|
+
# Prints UserWarning about schema mismatch
|
933
|
+
|
934
|
+
loaded_block.number_of_ducks = 42
|
935
|
+
|
936
|
+
loaded_block.save("my-custom-message", overwrite=True)
|
937
|
+
```
|
938
|
+
"""
|
939
|
+
if TYPE_CHECKING:
|
940
|
+
assert isinstance(client, PrefectClient)
|
941
|
+
block_document, _ = await cls._aget_block_document(name, client=client)
|
942
|
+
|
943
|
+
return cls._load_from_block_document(block_document, validate=validate)
|
944
|
+
|
945
|
+
@classmethod
|
946
|
+
@async_dispatch(aload)
|
947
|
+
def load(
|
833
948
|
cls,
|
834
949
|
name: str,
|
835
950
|
validate: bool = True,
|
@@ -910,9 +1025,19 @@ class Block(BaseModel, ABC):
|
|
910
1025
|
loaded_block.save("my-custom-message", overwrite=True)
|
911
1026
|
```
|
912
1027
|
"""
|
913
|
-
|
914
|
-
|
915
|
-
|
1028
|
+
# Need to use a `PrefectClient` here to ensure `Block.load` and `Block.aload` signatures match
|
1029
|
+
# TODO: replace with only sync client once all internal calls are updated to use `Block.aload` and `@async_dispatch` is removed
|
1030
|
+
if client is None:
|
1031
|
+
# If a client wasn't provided, we get to use a sync client
|
1032
|
+
from prefect.client.orchestration import get_client
|
1033
|
+
|
1034
|
+
with get_client(sync_client=True) as sync_client:
|
1035
|
+
block_document, _ = cls._get_block_document(name, client=sync_client)
|
1036
|
+
else:
|
1037
|
+
# If a client was provided, reuse it, even though it's async, to avoid excessive client creation
|
1038
|
+
block_document, _ = run_coro_as_sync(
|
1039
|
+
cls._aget_block_document(name, client=client)
|
1040
|
+
)
|
916
1041
|
|
917
1042
|
return cls._load_from_block_document(block_document, validate=validate)
|
918
1043
|
|
@@ -966,14 +1091,16 @@ class Block(BaseModel, ABC):
|
|
966
1091
|
"""
|
967
1092
|
block_document = None
|
968
1093
|
if isinstance(ref, (str, UUID)):
|
969
|
-
block_document, _ = await cls._get_block_document_by_id(ref)
|
1094
|
+
block_document, _ = await cls._get_block_document_by_id(ref, client=client)
|
970
1095
|
elif isinstance(ref, dict):
|
971
1096
|
if block_document_id := ref.get("block_document_id"):
|
972
1097
|
block_document, _ = await cls._get_block_document_by_id(
|
973
|
-
block_document_id
|
1098
|
+
block_document_id, client=client
|
974
1099
|
)
|
975
1100
|
elif block_document_slug := ref.get("block_document_slug"):
|
976
|
-
block_document, _ = await cls._get_block_document(
|
1101
|
+
block_document, _ = await cls._get_block_document(
|
1102
|
+
block_document_slug, client=client
|
1103
|
+
)
|
977
1104
|
|
978
1105
|
if not block_document:
|
979
1106
|
raise ValueError(f"Invalid reference format {ref!r}.")
|
@@ -1218,7 +1345,9 @@ class Block(BaseModel, ABC):
|
|
1218
1345
|
name: str,
|
1219
1346
|
client: Optional["PrefectClient"] = None,
|
1220
1347
|
):
|
1221
|
-
|
1348
|
+
if TYPE_CHECKING:
|
1349
|
+
assert isinstance(client, PrefectClient)
|
1350
|
+
block_document, _ = await cls._aget_block_document(name, client=client)
|
1222
1351
|
|
1223
1352
|
await client.delete_block_document(block_document.id)
|
1224
1353
|
|
@@ -1229,7 +1358,7 @@ class Block(BaseModel, ABC):
|
|
1229
1358
|
"""
|
1230
1359
|
block_type_slug = kwargs.pop("block_type_slug", None)
|
1231
1360
|
if block_type_slug:
|
1232
|
-
subcls =
|
1361
|
+
subcls = cls.get_block_class_from_key(block_type_slug)
|
1233
1362
|
return super().__new__(subcls)
|
1234
1363
|
else:
|
1235
1364
|
return super().__new__(cls)
|
prefect/blocks/system.py
CHANGED
@@ -9,10 +9,10 @@ from pydantic import (
|
|
9
9
|
field_validator,
|
10
10
|
)
|
11
11
|
from pydantic import Secret as PydanticSecret
|
12
|
-
from pydantic_extra_types.pendulum_dt import DateTime as PydanticDateTime
|
13
12
|
|
14
13
|
from prefect._internal.compatibility.deprecated import deprecated_class
|
15
14
|
from prefect.blocks.core import Block
|
15
|
+
from prefect.types import DateTime as PydanticDateTime
|
16
16
|
|
17
17
|
_SecretValueType = Union[
|
18
18
|
Annotated[StrictStr, Field(title="string")],
|
@@ -130,6 +130,7 @@ class Secret(Block, Generic[T]):
|
|
130
130
|
|
131
131
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/c6f20e556dd16effda9df16551feecfb5822092b-48x48.png"
|
132
132
|
_documentation_url = "https://docs.prefect.io/latest/develop/blocks"
|
133
|
+
_description = "A block that represents a secret value. The value stored in this block will be obfuscated when this block is viewed or edited in the UI."
|
133
134
|
|
134
135
|
value: Union[SecretStr, PydanticSecret[T]] = Field(
|
135
136
|
default=...,
|