prefect-client 2.20.4__py3-none-any.whl → 3.0.0__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 +74 -110
- prefect/_internal/compatibility/deprecated.py +6 -115
- prefect/_internal/compatibility/experimental.py +4 -79
- prefect/_internal/compatibility/migration.py +166 -0
- prefect/_internal/concurrency/__init__.py +2 -2
- prefect/_internal/concurrency/api.py +1 -35
- prefect/_internal/concurrency/calls.py +0 -6
- prefect/_internal/concurrency/cancellation.py +0 -3
- prefect/_internal/concurrency/event_loop.py +0 -20
- prefect/_internal/concurrency/inspection.py +3 -3
- prefect/_internal/concurrency/primitives.py +1 -0
- prefect/_internal/concurrency/services.py +23 -0
- prefect/_internal/concurrency/threads.py +35 -0
- prefect/_internal/concurrency/waiters.py +0 -28
- prefect/_internal/integrations.py +7 -0
- prefect/_internal/pydantic/__init__.py +0 -45
- prefect/_internal/pydantic/annotations/pendulum.py +2 -2
- prefect/_internal/pydantic/v1_schema.py +21 -22
- prefect/_internal/pydantic/v2_schema.py +0 -2
- prefect/_internal/pydantic/v2_validated_func.py +18 -23
- prefect/_internal/pytz.py +1 -1
- prefect/_internal/retries.py +61 -0
- prefect/_internal/schemas/bases.py +45 -177
- prefect/_internal/schemas/fields.py +1 -43
- prefect/_internal/schemas/validators.py +47 -233
- prefect/agent.py +3 -695
- prefect/artifacts.py +173 -14
- prefect/automations.py +39 -4
- prefect/blocks/abstract.py +1 -1
- prefect/blocks/core.py +405 -153
- prefect/blocks/fields.py +2 -57
- prefect/blocks/notifications.py +43 -28
- prefect/blocks/redis.py +168 -0
- prefect/blocks/system.py +67 -20
- prefect/blocks/webhook.py +2 -9
- prefect/cache_policies.py +239 -0
- prefect/client/__init__.py +4 -0
- prefect/client/base.py +33 -27
- prefect/client/cloud.py +65 -20
- prefect/client/collections.py +1 -1
- prefect/client/orchestration.py +650 -442
- prefect/client/schemas/actions.py +115 -100
- prefect/client/schemas/filters.py +46 -52
- prefect/client/schemas/objects.py +228 -178
- prefect/client/schemas/responses.py +18 -36
- prefect/client/schemas/schedules.py +55 -36
- prefect/client/schemas/sorting.py +2 -0
- prefect/client/subscriptions.py +8 -7
- prefect/client/types/flexible_schedule_list.py +11 -0
- prefect/client/utilities.py +9 -6
- prefect/concurrency/asyncio.py +60 -11
- prefect/concurrency/context.py +24 -0
- prefect/concurrency/events.py +2 -2
- prefect/concurrency/services.py +46 -16
- prefect/concurrency/sync.py +51 -7
- prefect/concurrency/v1/asyncio.py +143 -0
- prefect/concurrency/v1/context.py +27 -0
- prefect/concurrency/v1/events.py +61 -0
- prefect/concurrency/v1/services.py +116 -0
- prefect/concurrency/v1/sync.py +92 -0
- prefect/context.py +246 -149
- prefect/deployments/__init__.py +33 -18
- prefect/deployments/base.py +10 -15
- prefect/deployments/deployments.py +2 -1048
- prefect/deployments/flow_runs.py +178 -0
- prefect/deployments/runner.py +72 -173
- prefect/deployments/schedules.py +31 -25
- prefect/deployments/steps/__init__.py +0 -1
- prefect/deployments/steps/core.py +7 -0
- prefect/deployments/steps/pull.py +15 -21
- prefect/deployments/steps/utility.py +2 -1
- prefect/docker/__init__.py +20 -0
- prefect/docker/docker_image.py +82 -0
- prefect/engine.py +15 -2475
- prefect/events/actions.py +17 -23
- prefect/events/cli/automations.py +20 -7
- prefect/events/clients.py +142 -80
- prefect/events/filters.py +14 -18
- prefect/events/related.py +74 -75
- prefect/events/schemas/__init__.py +0 -5
- prefect/events/schemas/automations.py +55 -46
- prefect/events/schemas/deployment_triggers.py +7 -197
- prefect/events/schemas/events.py +46 -65
- prefect/events/schemas/labelling.py +10 -14
- prefect/events/utilities.py +4 -5
- prefect/events/worker.py +23 -8
- prefect/exceptions.py +15 -0
- prefect/filesystems.py +30 -529
- prefect/flow_engine.py +827 -0
- prefect/flow_runs.py +379 -7
- prefect/flows.py +470 -360
- prefect/futures.py +382 -331
- prefect/infrastructure/__init__.py +5 -26
- prefect/infrastructure/base.py +3 -320
- prefect/infrastructure/provisioners/__init__.py +5 -3
- prefect/infrastructure/provisioners/cloud_run.py +13 -8
- prefect/infrastructure/provisioners/container_instance.py +14 -9
- prefect/infrastructure/provisioners/ecs.py +10 -8
- prefect/infrastructure/provisioners/modal.py +8 -5
- prefect/input/__init__.py +4 -0
- prefect/input/actions.py +2 -4
- prefect/input/run_input.py +9 -9
- prefect/logging/formatters.py +2 -4
- prefect/logging/handlers.py +9 -14
- prefect/logging/loggers.py +5 -5
- prefect/main.py +72 -0
- prefect/plugins.py +2 -64
- prefect/profiles.toml +16 -2
- prefect/records/__init__.py +1 -0
- prefect/records/base.py +223 -0
- prefect/records/filesystem.py +207 -0
- prefect/records/memory.py +178 -0
- prefect/records/result_store.py +64 -0
- prefect/results.py +577 -504
- prefect/runner/runner.py +117 -47
- prefect/runner/server.py +32 -34
- prefect/runner/storage.py +3 -12
- prefect/runner/submit.py +2 -10
- prefect/runner/utils.py +2 -2
- prefect/runtime/__init__.py +1 -0
- prefect/runtime/deployment.py +1 -0
- prefect/runtime/flow_run.py +40 -5
- prefect/runtime/task_run.py +1 -0
- prefect/serializers.py +28 -39
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
- prefect/settings.py +209 -332
- prefect/states.py +160 -63
- prefect/task_engine.py +1478 -57
- prefect/task_runners.py +383 -287
- prefect/task_runs.py +240 -0
- prefect/task_worker.py +463 -0
- prefect/tasks.py +684 -374
- prefect/transactions.py +410 -0
- prefect/types/__init__.py +72 -86
- prefect/types/entrypoint.py +13 -0
- prefect/utilities/annotations.py +4 -3
- prefect/utilities/asyncutils.py +227 -148
- prefect/utilities/callables.py +137 -45
- prefect/utilities/collections.py +134 -86
- prefect/utilities/dispatch.py +27 -14
- prefect/utilities/dockerutils.py +11 -4
- prefect/utilities/engine.py +186 -32
- prefect/utilities/filesystem.py +4 -5
- prefect/utilities/importtools.py +26 -27
- prefect/utilities/pydantic.py +128 -38
- prefect/utilities/schema_tools/hydration.py +18 -1
- prefect/utilities/schema_tools/validation.py +30 -0
- prefect/utilities/services.py +35 -9
- prefect/utilities/templating.py +12 -2
- prefect/utilities/timeout.py +20 -5
- prefect/utilities/urls.py +195 -0
- prefect/utilities/visualization.py +1 -0
- prefect/variables.py +78 -59
- prefect/workers/__init__.py +0 -1
- prefect/workers/base.py +237 -244
- prefect/workers/block.py +5 -226
- prefect/workers/cloud.py +6 -0
- prefect/workers/process.py +265 -12
- prefect/workers/server.py +29 -11
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/METADATA +28 -24
- prefect_client-3.0.0.dist-info/RECORD +201 -0
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/WHEEL +1 -1
- prefect/_internal/pydantic/_base_model.py +0 -51
- prefect/_internal/pydantic/_compat.py +0 -82
- prefect/_internal/pydantic/_flags.py +0 -20
- prefect/_internal/pydantic/_types.py +0 -8
- prefect/_internal/pydantic/utilities/config_dict.py +0 -72
- prefect/_internal/pydantic/utilities/field_validator.py +0 -150
- prefect/_internal/pydantic/utilities/model_construct.py +0 -56
- prefect/_internal/pydantic/utilities/model_copy.py +0 -55
- prefect/_internal/pydantic/utilities/model_dump.py +0 -136
- prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
- prefect/_internal/pydantic/utilities/model_fields.py +0 -50
- prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
- prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
- prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
- prefect/_internal/pydantic/utilities/model_validate.py +0 -75
- prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
- prefect/_internal/pydantic/utilities/model_validator.py +0 -87
- prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
- prefect/_vendor/fastapi/__init__.py +0 -25
- prefect/_vendor/fastapi/applications.py +0 -946
- prefect/_vendor/fastapi/background.py +0 -3
- prefect/_vendor/fastapi/concurrency.py +0 -44
- prefect/_vendor/fastapi/datastructures.py +0 -58
- prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
- prefect/_vendor/fastapi/dependencies/models.py +0 -64
- prefect/_vendor/fastapi/dependencies/utils.py +0 -877
- prefect/_vendor/fastapi/encoders.py +0 -177
- prefect/_vendor/fastapi/exception_handlers.py +0 -40
- prefect/_vendor/fastapi/exceptions.py +0 -46
- prefect/_vendor/fastapi/logger.py +0 -3
- prefect/_vendor/fastapi/middleware/__init__.py +0 -1
- prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
- prefect/_vendor/fastapi/middleware/cors.py +0 -3
- prefect/_vendor/fastapi/middleware/gzip.py +0 -3
- prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
- prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
- prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
- prefect/_vendor/fastapi/openapi/__init__.py +0 -0
- prefect/_vendor/fastapi/openapi/constants.py +0 -2
- prefect/_vendor/fastapi/openapi/docs.py +0 -203
- prefect/_vendor/fastapi/openapi/models.py +0 -480
- prefect/_vendor/fastapi/openapi/utils.py +0 -485
- prefect/_vendor/fastapi/param_functions.py +0 -340
- prefect/_vendor/fastapi/params.py +0 -453
- prefect/_vendor/fastapi/py.typed +0 -0
- prefect/_vendor/fastapi/requests.py +0 -4
- prefect/_vendor/fastapi/responses.py +0 -40
- prefect/_vendor/fastapi/routing.py +0 -1331
- prefect/_vendor/fastapi/security/__init__.py +0 -15
- prefect/_vendor/fastapi/security/api_key.py +0 -98
- prefect/_vendor/fastapi/security/base.py +0 -6
- prefect/_vendor/fastapi/security/http.py +0 -172
- prefect/_vendor/fastapi/security/oauth2.py +0 -227
- prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
- prefect/_vendor/fastapi/security/utils.py +0 -10
- prefect/_vendor/fastapi/staticfiles.py +0 -1
- prefect/_vendor/fastapi/templating.py +0 -3
- prefect/_vendor/fastapi/testclient.py +0 -1
- prefect/_vendor/fastapi/types.py +0 -3
- prefect/_vendor/fastapi/utils.py +0 -235
- prefect/_vendor/fastapi/websockets.py +0 -7
- prefect/_vendor/starlette/__init__.py +0 -1
- prefect/_vendor/starlette/_compat.py +0 -28
- prefect/_vendor/starlette/_exception_handler.py +0 -80
- prefect/_vendor/starlette/_utils.py +0 -88
- prefect/_vendor/starlette/applications.py +0 -261
- prefect/_vendor/starlette/authentication.py +0 -159
- prefect/_vendor/starlette/background.py +0 -43
- prefect/_vendor/starlette/concurrency.py +0 -59
- prefect/_vendor/starlette/config.py +0 -151
- prefect/_vendor/starlette/convertors.py +0 -87
- prefect/_vendor/starlette/datastructures.py +0 -707
- prefect/_vendor/starlette/endpoints.py +0 -130
- prefect/_vendor/starlette/exceptions.py +0 -60
- prefect/_vendor/starlette/formparsers.py +0 -276
- prefect/_vendor/starlette/middleware/__init__.py +0 -17
- prefect/_vendor/starlette/middleware/authentication.py +0 -52
- prefect/_vendor/starlette/middleware/base.py +0 -220
- prefect/_vendor/starlette/middleware/cors.py +0 -176
- prefect/_vendor/starlette/middleware/errors.py +0 -265
- prefect/_vendor/starlette/middleware/exceptions.py +0 -74
- prefect/_vendor/starlette/middleware/gzip.py +0 -113
- prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
- prefect/_vendor/starlette/middleware/sessions.py +0 -82
- prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
- prefect/_vendor/starlette/middleware/wsgi.py +0 -147
- prefect/_vendor/starlette/py.typed +0 -0
- prefect/_vendor/starlette/requests.py +0 -328
- prefect/_vendor/starlette/responses.py +0 -347
- prefect/_vendor/starlette/routing.py +0 -933
- prefect/_vendor/starlette/schemas.py +0 -154
- prefect/_vendor/starlette/staticfiles.py +0 -248
- prefect/_vendor/starlette/status.py +0 -199
- prefect/_vendor/starlette/templating.py +0 -231
- prefect/_vendor/starlette/testclient.py +0 -804
- prefect/_vendor/starlette/types.py +0 -30
- prefect/_vendor/starlette/websockets.py +0 -193
- prefect/blocks/kubernetes.py +0 -119
- prefect/deprecated/__init__.py +0 -0
- prefect/deprecated/data_documents.py +0 -350
- prefect/deprecated/packaging/__init__.py +0 -12
- prefect/deprecated/packaging/base.py +0 -96
- prefect/deprecated/packaging/docker.py +0 -146
- prefect/deprecated/packaging/file.py +0 -92
- prefect/deprecated/packaging/orion.py +0 -80
- prefect/deprecated/packaging/serializers.py +0 -171
- prefect/events/instrument.py +0 -135
- prefect/infrastructure/container.py +0 -824
- prefect/infrastructure/kubernetes.py +0 -920
- prefect/infrastructure/process.py +0 -289
- prefect/manifests.py +0 -20
- prefect/new_flow_engine.py +0 -449
- prefect/new_task_engine.py +0 -423
- prefect/pydantic/__init__.py +0 -76
- prefect/pydantic/main.py +0 -39
- prefect/software/__init__.py +0 -2
- prefect/software/base.py +0 -50
- prefect/software/conda.py +0 -199
- prefect/software/pip.py +0 -122
- prefect/software/python.py +0 -52
- prefect/task_server.py +0 -322
- prefect_client-2.20.4.dist-info/RECORD +0 -294
- /prefect/{_internal/pydantic/utilities → client/types}/__init__.py +0 -0
- /prefect/{_vendor → concurrency/v1}/__init__.py +0 -0
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/top_level.txt +0 -0
prefect/blocks/core.py
CHANGED
@@ -2,37 +2,46 @@ import hashlib
|
|
2
2
|
import html
|
3
3
|
import inspect
|
4
4
|
import sys
|
5
|
+
import uuid
|
5
6
|
import warnings
|
6
7
|
from abc import ABC
|
8
|
+
from functools import partial
|
7
9
|
from textwrap import dedent
|
8
10
|
from typing import (
|
9
11
|
TYPE_CHECKING,
|
10
12
|
Any,
|
13
|
+
Callable,
|
14
|
+
ClassVar,
|
11
15
|
Dict,
|
12
16
|
FrozenSet,
|
13
17
|
List,
|
14
18
|
Optional,
|
19
|
+
Tuple,
|
15
20
|
Type,
|
16
21
|
TypeVar,
|
17
22
|
Union,
|
23
|
+
get_origin,
|
18
24
|
)
|
19
25
|
from uuid import UUID, uuid4
|
20
26
|
|
21
27
|
from griffe import Docstring, DocstringSection, DocstringSectionKind, Parser, parse
|
22
28
|
from packaging.version import InvalidVersion, Version
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
from pydantic import (
|
30
|
+
BaseModel,
|
31
|
+
ConfigDict,
|
32
|
+
HttpUrl,
|
33
|
+
PrivateAttr,
|
34
|
+
SecretBytes,
|
35
|
+
SecretStr,
|
36
|
+
SerializationInfo,
|
37
|
+
ValidationError,
|
38
|
+
model_serializer,
|
39
|
+
)
|
40
|
+
from pydantic.json_schema import GenerateJsonSchema
|
41
|
+
from typing_extensions import Literal, ParamSpec, Self, get_args
|
32
42
|
|
33
43
|
import prefect
|
34
44
|
import prefect.exceptions
|
35
|
-
from prefect.blocks.fields import SecretDict
|
36
45
|
from prefect.client.schemas import (
|
37
46
|
DEFAULT_BLOCK_SCHEMA_VERSION,
|
38
47
|
BlockDocument,
|
@@ -41,26 +50,27 @@ from prefect.client.schemas import (
|
|
41
50
|
BlockTypeUpdate,
|
42
51
|
)
|
43
52
|
from prefect.client.utilities import inject_client
|
44
|
-
from prefect.events
|
45
|
-
ResourceTuple,
|
46
|
-
emit_instance_method_called_event,
|
47
|
-
instrument_instance_method_call,
|
48
|
-
instrument_method_calls_on_class_instances,
|
49
|
-
)
|
53
|
+
from prefect.events import emit_event
|
50
54
|
from prefect.logging.loggers import disable_logger
|
55
|
+
from prefect.types import SecretDict
|
51
56
|
from prefect.utilities.asyncutils import sync_compatible
|
52
|
-
from prefect.utilities.collections import listrepr, remove_nested_keys
|
57
|
+
from prefect.utilities.collections import listrepr, remove_nested_keys, visit_collection
|
53
58
|
from prefect.utilities.dispatch import lookup_type, register_base_type
|
54
59
|
from prefect.utilities.hashing import hash_objects
|
55
60
|
from prefect.utilities.importtools import to_qualified_name
|
61
|
+
from prefect.utilities.pydantic import handle_secret_render
|
56
62
|
from prefect.utilities.slugify import slugify
|
57
63
|
|
58
64
|
if TYPE_CHECKING:
|
65
|
+
from pydantic.main import IncEx
|
66
|
+
|
59
67
|
from prefect.client.orchestration import PrefectClient
|
60
68
|
|
61
69
|
R = TypeVar("R")
|
62
70
|
P = ParamSpec("P")
|
63
71
|
|
72
|
+
ResourceTuple = Tuple[Dict[str, Any], List[Dict[str, Any]]]
|
73
|
+
|
64
74
|
|
65
75
|
def block_schema_to_key(schema: BlockSchema) -> str:
|
66
76
|
"""
|
@@ -123,7 +133,9 @@ def _is_subclass(cls, parent_cls) -> bool:
|
|
123
133
|
return inspect.isclass(cls) and not get_origin(cls) and issubclass(cls, parent_cls)
|
124
134
|
|
125
135
|
|
126
|
-
def _collect_secret_fields(
|
136
|
+
def _collect_secret_fields(
|
137
|
+
name: str, type_: Type[BaseModel], secrets: List[str]
|
138
|
+
) -> None:
|
127
139
|
"""
|
128
140
|
Recursively collects all secret fields from a given type and adds them to the
|
129
141
|
secrets list, supporting nested Union / Dict / Tuple / List / BaseModel fields.
|
@@ -134,18 +146,24 @@ def _collect_secret_fields(name: str, type_: Type, secrets: List[str]) -> None:
|
|
134
146
|
_collect_secret_fields(name, nested_type, secrets)
|
135
147
|
return
|
136
148
|
elif _is_subclass(type_, BaseModel):
|
137
|
-
for field in type_.
|
138
|
-
_collect_secret_fields(f"{name}.{
|
149
|
+
for field_name, field in type_.model_fields.items():
|
150
|
+
_collect_secret_fields(f"{name}.{field_name}", field.annotation, secrets)
|
139
151
|
return
|
140
152
|
|
141
|
-
if type_ in (SecretStr, SecretBytes)
|
153
|
+
if type_ in (SecretStr, SecretBytes) or (
|
154
|
+
isinstance(type_, type)
|
155
|
+
and getattr(type_, "__module__", None) == "pydantic.types"
|
156
|
+
and getattr(type_, "__name__", None) == "Secret"
|
157
|
+
):
|
142
158
|
secrets.append(name)
|
143
159
|
elif type_ == SecretDict:
|
144
160
|
# Append .* to field name to signify that all values under this
|
145
161
|
# field are secret and should be obfuscated.
|
146
162
|
secrets.append(f"{name}.*")
|
147
163
|
elif Block.is_block_class(type_):
|
148
|
-
secrets.extend(
|
164
|
+
secrets.extend(
|
165
|
+
f"{name}.{s}" for s in type_.model_json_schema()["secret_fields"]
|
166
|
+
)
|
149
167
|
|
150
168
|
|
151
169
|
def _should_update_block_type(
|
@@ -158,8 +176,10 @@ def _should_update_block_type(
|
|
158
176
|
"""
|
159
177
|
fields = BlockTypeUpdate.updatable_fields()
|
160
178
|
|
161
|
-
local_block_fields = local_block_type.
|
162
|
-
server_block_fields = server_block_type.
|
179
|
+
local_block_fields = local_block_type.model_dump(include=fields, exclude_unset=True)
|
180
|
+
server_block_fields = server_block_type.model_dump(
|
181
|
+
include=fields, exclude_unset=True
|
182
|
+
)
|
163
183
|
|
164
184
|
if local_block_fields.get("description") is not None:
|
165
185
|
local_block_fields["description"] = html.unescape(
|
@@ -191,8 +211,53 @@ class BlockNotSavedError(RuntimeError):
|
|
191
211
|
pass
|
192
212
|
|
193
213
|
|
214
|
+
def schema_extra(schema: Dict[str, Any], model: Type["Block"]):
|
215
|
+
"""
|
216
|
+
Customizes Pydantic's schema generation feature to add blocks related information.
|
217
|
+
"""
|
218
|
+
schema["block_type_slug"] = model.get_block_type_slug()
|
219
|
+
# Ensures args and code examples aren't included in the schema
|
220
|
+
description = model.get_description()
|
221
|
+
if description:
|
222
|
+
schema["description"] = description
|
223
|
+
else:
|
224
|
+
# Prevent the description of the base class from being included in the schema
|
225
|
+
schema.pop("description", None)
|
226
|
+
|
227
|
+
# create a list of secret field names
|
228
|
+
# secret fields include both top-level keys and dot-delimited nested secret keys
|
229
|
+
# A wildcard (*) means that all fields under a given key are secret.
|
230
|
+
# for example: ["x", "y", "z.*", "child.a"]
|
231
|
+
# means the top-level keys "x" and "y", all keys under "z", and the key "a" of a block
|
232
|
+
# nested under the "child" key are all secret. There is no limit to nesting.
|
233
|
+
secrets = schema["secret_fields"] = []
|
234
|
+
for name, field in model.model_fields.items():
|
235
|
+
_collect_secret_fields(name, field.annotation, secrets)
|
236
|
+
|
237
|
+
# create block schema references
|
238
|
+
refs = schema["block_schema_references"] = {}
|
239
|
+
|
240
|
+
def collect_block_schema_references(field_name: str, annotation: type) -> None:
|
241
|
+
"""Walk through the annotation and collect block schemas for any nested blocks."""
|
242
|
+
if Block.is_block_class(annotation):
|
243
|
+
if isinstance(refs.get(field_name), list):
|
244
|
+
refs[field_name].append(annotation._to_block_schema_reference_dict())
|
245
|
+
elif isinstance(refs.get(field_name), dict):
|
246
|
+
refs[field_name] = [
|
247
|
+
refs[field_name],
|
248
|
+
annotation._to_block_schema_reference_dict(),
|
249
|
+
]
|
250
|
+
else:
|
251
|
+
refs[field_name] = annotation._to_block_schema_reference_dict()
|
252
|
+
if get_origin(annotation) in (Union, list, tuple, dict):
|
253
|
+
for type_ in get_args(annotation):
|
254
|
+
collect_block_schema_references(field_name, type_)
|
255
|
+
|
256
|
+
for name, field in model.model_fields.items():
|
257
|
+
collect_block_schema_references(name, field.annotation)
|
258
|
+
|
259
|
+
|
194
260
|
@register_base_type
|
195
|
-
@instrument_method_calls_on_class_instances
|
196
261
|
class Block(BaseModel, ABC):
|
197
262
|
"""
|
198
263
|
A base class for implementing a block that wraps an external service.
|
@@ -210,60 +275,10 @@ class Block(BaseModel, ABC):
|
|
210
275
|
initialization.
|
211
276
|
"""
|
212
277
|
|
213
|
-
|
214
|
-
extra
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
@staticmethod
|
219
|
-
def schema_extra(schema: Dict[str, Any], model: Type["Block"]):
|
220
|
-
"""
|
221
|
-
Customizes Pydantic's schema generation feature to add blocks related information.
|
222
|
-
"""
|
223
|
-
schema["block_type_slug"] = model.get_block_type_slug()
|
224
|
-
# Ensures args and code examples aren't included in the schema
|
225
|
-
description = model.get_description()
|
226
|
-
if description:
|
227
|
-
schema["description"] = description
|
228
|
-
else:
|
229
|
-
# Prevent the description of the base class from being included in the schema
|
230
|
-
schema.pop("description", None)
|
231
|
-
|
232
|
-
# create a list of secret field names
|
233
|
-
# secret fields include both top-level keys and dot-delimited nested secret keys
|
234
|
-
# A wildcard (*) means that all fields under a given key are secret.
|
235
|
-
# for example: ["x", "y", "z.*", "child.a"]
|
236
|
-
# means the top-level keys "x" and "y", all keys under "z", and the key "a" of a block
|
237
|
-
# nested under the "child" key are all secret. There is no limit to nesting.
|
238
|
-
secrets = schema["secret_fields"] = []
|
239
|
-
for field in model.__fields__.values():
|
240
|
-
_collect_secret_fields(field.name, field.type_, secrets)
|
241
|
-
|
242
|
-
# create block schema references
|
243
|
-
refs = schema["block_schema_references"] = {}
|
244
|
-
|
245
|
-
def collect_block_schema_references(
|
246
|
-
field_name: str, annotation: type
|
247
|
-
) -> None:
|
248
|
-
"""Walk through the annotation and collect block schemas for any nested blocks."""
|
249
|
-
if Block.is_block_class(annotation):
|
250
|
-
if isinstance(refs.get(field_name), list):
|
251
|
-
refs[field_name].append(
|
252
|
-
annotation._to_block_schema_reference_dict()
|
253
|
-
)
|
254
|
-
elif isinstance(refs.get(field_name), dict):
|
255
|
-
refs[field_name] = [
|
256
|
-
refs[field_name],
|
257
|
-
annotation._to_block_schema_reference_dict(),
|
258
|
-
]
|
259
|
-
else:
|
260
|
-
refs[field_name] = annotation._to_block_schema_reference_dict()
|
261
|
-
if get_origin(annotation) in (Union, list, tuple, dict):
|
262
|
-
for type_ in get_args(annotation):
|
263
|
-
collect_block_schema_references(field_name, type_)
|
264
|
-
|
265
|
-
for field in model.__fields__.values():
|
266
|
-
collect_block_schema_references(field.name, field.type_)
|
278
|
+
model_config = ConfigDict(
|
279
|
+
extra="allow",
|
280
|
+
json_schema_extra=schema_extra,
|
281
|
+
)
|
267
282
|
|
268
283
|
def __init__(self, *args, **kwargs):
|
269
284
|
super().__init__(*args, **kwargs)
|
@@ -274,7 +289,7 @@ class Block(BaseModel, ABC):
|
|
274
289
|
|
275
290
|
def __repr_args__(self):
|
276
291
|
repr_args = super().__repr_args__()
|
277
|
-
data_keys = self.
|
292
|
+
data_keys = self.model_json_schema()["properties"].keys()
|
278
293
|
return [
|
279
294
|
(key, value) for key, value in repr_args if key is None or key in data_keys
|
280
295
|
]
|
@@ -288,25 +303,26 @@ class Block(BaseModel, ABC):
|
|
288
303
|
# Attribute to customize the name of the block type created
|
289
304
|
# when the block is registered with the API. If not set, block
|
290
305
|
# type name will default to the class name.
|
291
|
-
_block_type_name: Optional[str] = None
|
292
|
-
_block_type_slug: Optional[str] = None
|
306
|
+
_block_type_name: ClassVar[Optional[str]] = None
|
307
|
+
_block_type_slug: ClassVar[Optional[str]] = None
|
293
308
|
|
294
309
|
# Attributes used to set properties on a block type when registered
|
295
310
|
# with the API.
|
296
|
-
_logo_url: Optional[HttpUrl] = None
|
297
|
-
_documentation_url: Optional[HttpUrl] = None
|
298
|
-
_description: Optional[str] = None
|
299
|
-
_code_example: Optional[str] = None
|
311
|
+
_logo_url: ClassVar[Optional[HttpUrl]] = None
|
312
|
+
_documentation_url: ClassVar[Optional[HttpUrl]] = None
|
313
|
+
_description: ClassVar[Optional[str]] = None
|
314
|
+
_code_example: ClassVar[Optional[str]] = None
|
315
|
+
_block_type_id: ClassVar[Optional[UUID]] = None
|
316
|
+
_block_schema_id: ClassVar[Optional[UUID]] = None
|
317
|
+
_block_schema_capabilities: ClassVar[Optional[List[str]]] = None
|
318
|
+
_block_schema_version: ClassVar[Optional[str]] = None
|
300
319
|
|
301
320
|
# -- private instance variables
|
302
321
|
# these are set when blocks are loaded from the API
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
_block_document_id: Optional[UUID] = None
|
308
|
-
_block_document_name: Optional[str] = None
|
309
|
-
_is_anonymous: Optional[bool] = None
|
322
|
+
|
323
|
+
_block_document_id: Optional[UUID] = PrivateAttr(None)
|
324
|
+
_block_document_name: Optional[str] = PrivateAttr(None)
|
325
|
+
_is_anonymous: Optional[bool] = PrivateAttr(None)
|
310
326
|
|
311
327
|
# Exclude `save` as it uses the `sync_compatible` decorator and needs to be
|
312
328
|
# decorated directly.
|
@@ -318,6 +334,31 @@ class Block(BaseModel, ABC):
|
|
318
334
|
return None # The base class is abstract
|
319
335
|
return block_schema_to_key(cls._to_block_schema())
|
320
336
|
|
337
|
+
@model_serializer(mode="wrap")
|
338
|
+
def ser_model(self, handler: Callable, info: SerializationInfo) -> Any:
|
339
|
+
jsonable_self = handler(self)
|
340
|
+
if (ctx := info.context) and ctx.get("include_secrets") is True:
|
341
|
+
jsonable_self.update(
|
342
|
+
{
|
343
|
+
field_name: visit_collection(
|
344
|
+
expr=getattr(self, field_name),
|
345
|
+
visit_fn=partial(handle_secret_render, context=ctx),
|
346
|
+
return_data=True,
|
347
|
+
)
|
348
|
+
for field_name in self.model_fields
|
349
|
+
}
|
350
|
+
)
|
351
|
+
if extra_fields := {
|
352
|
+
"block_type_slug": self.get_block_type_slug(),
|
353
|
+
"_block_document_id": self._block_document_id,
|
354
|
+
"_block_document_name": self._block_document_name,
|
355
|
+
"_is_anonymous": self._is_anonymous,
|
356
|
+
}:
|
357
|
+
jsonable_self |= {
|
358
|
+
key: value for key, value in extra_fields.items() if value is not None
|
359
|
+
}
|
360
|
+
return jsonable_self
|
361
|
+
|
321
362
|
@classmethod
|
322
363
|
def get_block_type_name(cls):
|
323
364
|
return cls._block_type_name or cls.__name__
|
@@ -383,7 +424,9 @@ class Block(BaseModel, ABC):
|
|
383
424
|
str: The calculated checksum prefixed with the hashing algorithm used.
|
384
425
|
"""
|
385
426
|
block_schema_fields = (
|
386
|
-
cls.
|
427
|
+
cls.model_json_schema()
|
428
|
+
if block_schema_fields is None
|
429
|
+
else block_schema_fields
|
387
430
|
)
|
388
431
|
fields_for_checksum = remove_nested_keys(["secret_fields"], block_schema_fields)
|
389
432
|
if fields_for_checksum.get("definitions"):
|
@@ -408,6 +451,7 @@ class Block(BaseModel, ABC):
|
|
408
451
|
block_schema_id: Optional[UUID] = None,
|
409
452
|
block_type_id: Optional[UUID] = None,
|
410
453
|
is_anonymous: Optional[bool] = None,
|
454
|
+
include_secrets: bool = False,
|
411
455
|
) -> BlockDocument:
|
412
456
|
"""
|
413
457
|
Creates the corresponding block document based on the data stored in a block.
|
@@ -445,10 +489,14 @@ class Block(BaseModel, ABC):
|
|
445
489
|
# The keys passed to `include` must NOT be aliases, else some items will be missed
|
446
490
|
# i.e. must do `self.schema_` vs `self.schema` to get a `schema_ = Field(alias="schema")`
|
447
491
|
# reported from https://github.com/PrefectHQ/prefect-dbt/issues/54
|
448
|
-
data_keys = self.
|
492
|
+
data_keys = self.model_json_schema(by_alias=False)["properties"].keys()
|
449
493
|
|
450
494
|
# `block_document_data`` must return the aliased version for it to show in the UI
|
451
|
-
block_document_data = self.
|
495
|
+
block_document_data = self.model_dump(
|
496
|
+
by_alias=True,
|
497
|
+
include=data_keys,
|
498
|
+
context={"include_secrets": include_secrets},
|
499
|
+
)
|
452
500
|
|
453
501
|
# Iterate through and find blocks that already have saved block documents to
|
454
502
|
# create references to those saved block documents.
|
@@ -488,7 +536,7 @@ class Block(BaseModel, ABC):
|
|
488
536
|
Returns:
|
489
537
|
BlockSchema: The corresponding block schema.
|
490
538
|
"""
|
491
|
-
fields = cls.
|
539
|
+
fields = cls.model_json_schema()
|
492
540
|
return BlockSchema(
|
493
541
|
id=cls._block_schema_id if cls._block_schema_id is not None else uuid4(),
|
494
542
|
checksum=cls._calculate_schema_checksum(),
|
@@ -543,7 +591,7 @@ class Block(BaseModel, ABC):
|
|
543
591
|
code example from the class docstring if an override is not provided.
|
544
592
|
"""
|
545
593
|
code_example = (
|
546
|
-
dedent(cls._code_example) if cls._code_example is not None else None
|
594
|
+
dedent(text=cls._code_example) if cls._code_example is not None else None
|
547
595
|
)
|
548
596
|
# If no code example override has been provided, attempt to find a examples
|
549
597
|
# section or an admonition with the annotation "example" and use that as the
|
@@ -610,7 +658,7 @@ class Block(BaseModel, ABC):
|
|
610
658
|
)
|
611
659
|
|
612
660
|
@classmethod
|
613
|
-
def _from_block_document(cls, block_document: BlockDocument):
|
661
|
+
def _from_block_document(cls, block_document: BlockDocument) -> Self:
|
614
662
|
"""
|
615
663
|
Instantiates a block from a given block document. The corresponding block class
|
616
664
|
will be looked up in the block registry based on the corresponding block schema
|
@@ -638,9 +686,7 @@ class Block(BaseModel, ABC):
|
|
638
686
|
else cls.get_block_class_from_schema(block_document.block_schema)
|
639
687
|
)
|
640
688
|
|
641
|
-
|
642
|
-
|
643
|
-
block = block_cls.parse_obj(block_document.data)
|
689
|
+
block = block_cls.model_validate(block_document.data)
|
644
690
|
block._block_document_id = block_document.id
|
645
691
|
block.__class__._block_schema_id = block_document.block_schema_id
|
646
692
|
block.__class__._block_type_id = block_document.block_type_id
|
@@ -650,13 +696,11 @@ class Block(BaseModel, ABC):
|
|
650
696
|
block_document.block_document_references
|
651
697
|
)
|
652
698
|
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
emit_instance_method_called_event(block, "load", successful=True)
|
699
|
+
resources: Optional[ResourceTuple] = block._event_method_called_resources()
|
700
|
+
if resources:
|
701
|
+
kind = block._event_kind()
|
702
|
+
resource, related = resources
|
703
|
+
emit_event(event=f"{kind}.loaded", resource=resource, related=related)
|
660
704
|
|
661
705
|
return block
|
662
706
|
|
@@ -735,7 +779,7 @@ class Block(BaseModel, ABC):
|
|
735
779
|
async def _get_block_document(
|
736
780
|
cls,
|
737
781
|
name: str,
|
738
|
-
client: "PrefectClient" = None,
|
782
|
+
client: Optional["PrefectClient"] = None,
|
739
783
|
):
|
740
784
|
if cls.__name__ == "Block":
|
741
785
|
block_type_slug, block_document_name = name.split("/", 1)
|
@@ -755,6 +799,33 @@ class Block(BaseModel, ABC):
|
|
755
799
|
|
756
800
|
return block_document, block_document_name
|
757
801
|
|
802
|
+
@classmethod
|
803
|
+
@sync_compatible
|
804
|
+
@inject_client
|
805
|
+
async def _get_block_document_by_id(
|
806
|
+
cls,
|
807
|
+
block_document_id: Union[str, uuid.UUID],
|
808
|
+
client: Optional["PrefectClient"] = None,
|
809
|
+
):
|
810
|
+
if isinstance(block_document_id, str):
|
811
|
+
try:
|
812
|
+
block_document_id = UUID(block_document_id)
|
813
|
+
except ValueError:
|
814
|
+
raise ValueError(
|
815
|
+
f"Block document ID {block_document_id!r} is not a valid UUID"
|
816
|
+
)
|
817
|
+
|
818
|
+
try:
|
819
|
+
block_document = await client.read_block_document(
|
820
|
+
block_document_id=block_document_id
|
821
|
+
)
|
822
|
+
except prefect.exceptions.ObjectNotFound:
|
823
|
+
raise ValueError(
|
824
|
+
f"Unable to find block document with ID {block_document_id!r}"
|
825
|
+
)
|
826
|
+
|
827
|
+
return block_document, block_document.name
|
828
|
+
|
758
829
|
@classmethod
|
759
830
|
@sync_compatible
|
760
831
|
@inject_client
|
@@ -762,8 +833,8 @@ class Block(BaseModel, ABC):
|
|
762
833
|
cls,
|
763
834
|
name: str,
|
764
835
|
validate: bool = True,
|
765
|
-
client: "PrefectClient" = None,
|
766
|
-
):
|
836
|
+
client: Optional["PrefectClient"] = None,
|
837
|
+
) -> "Self":
|
767
838
|
"""
|
768
839
|
Retrieves data from the block document with the given name for the block type
|
769
840
|
that corresponds with the current class and returns an instantiated version of
|
@@ -839,8 +910,108 @@ class Block(BaseModel, ABC):
|
|
839
910
|
loaded_block.save("my-custom-message", overwrite=True)
|
840
911
|
```
|
841
912
|
"""
|
842
|
-
block_document, block_document_name = await cls._get_block_document(
|
913
|
+
block_document, block_document_name = await cls._get_block_document(
|
914
|
+
name, client=client
|
915
|
+
)
|
916
|
+
|
917
|
+
return cls._load_from_block_document(block_document, validate=validate)
|
918
|
+
|
919
|
+
@classmethod
|
920
|
+
@sync_compatible
|
921
|
+
@inject_client
|
922
|
+
async def load_from_ref(
|
923
|
+
cls,
|
924
|
+
ref: Union[str, UUID, Dict[str, Any]],
|
925
|
+
validate: bool = True,
|
926
|
+
client: Optional["PrefectClient"] = None,
|
927
|
+
) -> "Self":
|
928
|
+
"""
|
929
|
+
Retrieves data from the block document by given reference for the block type
|
930
|
+
that corresponds with the current class and returns an instantiated version of
|
931
|
+
the current class with the data stored in the block document.
|
932
|
+
|
933
|
+
Provided reference can be a block document ID, or a reference data in dictionary format.
|
934
|
+
Supported dictionary reference formats are:
|
935
|
+
- {"block_document_id": <block_document_id>}
|
936
|
+
- {"block_document_slug": <block_document_slug>}
|
937
|
+
|
938
|
+
If a block document for a given block type is saved with a different schema
|
939
|
+
than the current class calling `load`, a warning will be raised.
|
940
|
+
|
941
|
+
If the current class schema is a subset of the block document schema, the block
|
942
|
+
can be loaded as normal using the default `validate = True`.
|
843
943
|
|
944
|
+
If the current class schema is a superset of the block document schema, `load`
|
945
|
+
must be called with `validate` set to False to prevent a validation error. In
|
946
|
+
this case, the block attributes will default to `None` and must be set manually
|
947
|
+
and saved to a new block document before the block can be used as expected.
|
948
|
+
|
949
|
+
Args:
|
950
|
+
ref: The reference to the block document. This can be a block document ID,
|
951
|
+
or one of supported dictionary reference formats.
|
952
|
+
validate: If False, the block document will be loaded without Pydantic
|
953
|
+
validating the block schema. This is useful if the block schema has
|
954
|
+
changed client-side since the block document referred to by `name` was saved.
|
955
|
+
client: The client to use to load the block document. If not provided, the
|
956
|
+
default client will be injected.
|
957
|
+
|
958
|
+
Raises:
|
959
|
+
ValueError: If invalid reference format is provided.
|
960
|
+
ValueError: If the requested block document is not found.
|
961
|
+
|
962
|
+
Returns:
|
963
|
+
An instance of the current class hydrated with the data stored in the
|
964
|
+
block document with the specified name.
|
965
|
+
|
966
|
+
"""
|
967
|
+
block_document = None
|
968
|
+
if isinstance(ref, (str, UUID)):
|
969
|
+
block_document, _ = await cls._get_block_document_by_id(ref)
|
970
|
+
elif isinstance(ref, dict):
|
971
|
+
if block_document_id := ref.get("block_document_id"):
|
972
|
+
block_document, _ = await cls._get_block_document_by_id(
|
973
|
+
block_document_id
|
974
|
+
)
|
975
|
+
elif block_document_slug := ref.get("block_document_slug"):
|
976
|
+
block_document, _ = await cls._get_block_document(block_document_slug)
|
977
|
+
|
978
|
+
if not block_document:
|
979
|
+
raise ValueError(f"Invalid reference format {ref!r}.")
|
980
|
+
|
981
|
+
return cls._load_from_block_document(block_document, validate=validate)
|
982
|
+
|
983
|
+
@classmethod
|
984
|
+
def _load_from_block_document(
|
985
|
+
cls, block_document: BlockDocument, validate: bool = True
|
986
|
+
) -> "Self":
|
987
|
+
"""
|
988
|
+
Loads a block from a given block document.
|
989
|
+
|
990
|
+
If a block document for a given block type is saved with a different schema
|
991
|
+
than the current class calling `load`, a warning will be raised.
|
992
|
+
|
993
|
+
If the current class schema is a subset of the block document schema, the block
|
994
|
+
can be loaded as normal using the default `validate = True`.
|
995
|
+
|
996
|
+
If the current class schema is a superset of the block document schema, `load`
|
997
|
+
must be called with `validate` set to False to prevent a validation error. In
|
998
|
+
this case, the block attributes will default to `None` and must be set manually
|
999
|
+
and saved to a new block document before the block can be used as expected.
|
1000
|
+
|
1001
|
+
Args:
|
1002
|
+
block_document: The block document used to instantiate a block.
|
1003
|
+
validate: If False, the block document will be loaded without Pydantic
|
1004
|
+
validating the block schema. This is useful if the block schema has
|
1005
|
+
changed client-side since the block document referred to by `name` was saved.
|
1006
|
+
|
1007
|
+
Raises:
|
1008
|
+
ValueError: If the requested block document is not found.
|
1009
|
+
|
1010
|
+
Returns:
|
1011
|
+
An instance of the current class hydrated with the data stored in the
|
1012
|
+
block document with the specified name.
|
1013
|
+
|
1014
|
+
"""
|
844
1015
|
try:
|
845
1016
|
return cls._from_block_document(block_document)
|
846
1017
|
except ValidationError as e:
|
@@ -848,19 +1019,19 @@ class Block(BaseModel, ABC):
|
|
848
1019
|
missing_fields = tuple(err["loc"][0] for err in e.errors())
|
849
1020
|
missing_block_data = {field: None for field in missing_fields}
|
850
1021
|
warnings.warn(
|
851
|
-
f"Could not fully load {
|
852
|
-
f" {cls.
|
1022
|
+
f"Could not fully load {block_document.name!r} of block type"
|
1023
|
+
f" {cls.get_block_type_slug()!r} - this is likely because one or more"
|
853
1024
|
" required fields were added to the schema for"
|
854
1025
|
f" {cls.__name__!r} that did not exist on the class when this block"
|
855
1026
|
" was last saved. Please specify values for new field(s):"
|
856
1027
|
f" {listrepr(missing_fields)}, then run"
|
857
|
-
f' `{cls.__name__}.save("{
|
1028
|
+
f' `{cls.__name__}.save("{block_document.name}", overwrite=True)`,'
|
858
1029
|
" and load this block again before attempting to use it."
|
859
1030
|
)
|
860
|
-
return cls.
|
1031
|
+
return cls.model_construct(**block_document.data, **missing_block_data)
|
861
1032
|
raise RuntimeError(
|
862
|
-
f"Unable to load {
|
863
|
-
f" {cls.
|
1033
|
+
f"Unable to load {block_document.name!r} of block type"
|
1034
|
+
f" {cls.get_block_type_slug()!r} due to failed validation. To load without"
|
864
1035
|
" validation, try loading again with `validate=False`."
|
865
1036
|
) from e
|
866
1037
|
|
@@ -868,10 +1039,22 @@ class Block(BaseModel, ABC):
|
|
868
1039
|
def is_block_class(block) -> bool:
|
869
1040
|
return _is_subclass(block, Block)
|
870
1041
|
|
1042
|
+
@staticmethod
|
1043
|
+
def annotation_refers_to_block_class(annotation: Any) -> bool:
|
1044
|
+
if Block.is_block_class(annotation):
|
1045
|
+
return True
|
1046
|
+
|
1047
|
+
if get_origin(annotation) is Union:
|
1048
|
+
for annotation in get_args(annotation):
|
1049
|
+
if Block.is_block_class(annotation):
|
1050
|
+
return True
|
1051
|
+
|
1052
|
+
return False
|
1053
|
+
|
871
1054
|
@classmethod
|
872
1055
|
@sync_compatible
|
873
1056
|
@inject_client
|
874
|
-
async def register_type_and_schema(cls, client: "PrefectClient" = None):
|
1057
|
+
async def register_type_and_schema(cls, client: Optional["PrefectClient"] = None):
|
875
1058
|
"""
|
876
1059
|
Makes block available for configuration with current Prefect API.
|
877
1060
|
Recursively registers all nested blocks. Registration is idempotent.
|
@@ -900,14 +1083,16 @@ class Block(BaseModel, ABC):
|
|
900
1083
|
for inner_annotation in get_args(annotation):
|
901
1084
|
await register_blocks_in_annotation(inner_annotation)
|
902
1085
|
|
903
|
-
for field in cls.
|
1086
|
+
for field in cls.model_fields.values():
|
904
1087
|
await register_blocks_in_annotation(field.annotation)
|
905
1088
|
|
906
1089
|
try:
|
907
1090
|
block_type = await client.read_block_type_by_slug(
|
908
1091
|
slug=cls.get_block_type_slug()
|
909
1092
|
)
|
1093
|
+
|
910
1094
|
cls._block_type_id = block_type.id
|
1095
|
+
|
911
1096
|
local_block_type = cls._to_block_type()
|
912
1097
|
if _should_update_block_type(
|
913
1098
|
local_block_type=local_block_type, server_block_type=block_type
|
@@ -937,7 +1122,7 @@ class Block(BaseModel, ABC):
|
|
937
1122
|
name: Optional[str] = None,
|
938
1123
|
is_anonymous: bool = False,
|
939
1124
|
overwrite: bool = False,
|
940
|
-
client: "PrefectClient" = None,
|
1125
|
+
client: Optional["PrefectClient"] = None,
|
941
1126
|
):
|
942
1127
|
"""
|
943
1128
|
Saves the values of a block as a block document with an option to save as an
|
@@ -985,7 +1170,9 @@ class Block(BaseModel, ABC):
|
|
985
1170
|
block_document_id = existing_block_document.id
|
986
1171
|
await client.update_block_document(
|
987
1172
|
block_document_id=block_document_id,
|
988
|
-
block_document=self._to_block_document(
|
1173
|
+
block_document=self._to_block_document(
|
1174
|
+
name=name, include_secrets=True
|
1175
|
+
),
|
989
1176
|
)
|
990
1177
|
block_document = await client.read_block_document(
|
991
1178
|
block_document_id=block_document_id
|
@@ -1003,12 +1190,11 @@ class Block(BaseModel, ABC):
|
|
1003
1190
|
return self._block_document_id
|
1004
1191
|
|
1005
1192
|
@sync_compatible
|
1006
|
-
@instrument_instance_method_call
|
1007
1193
|
async def save(
|
1008
1194
|
self,
|
1009
1195
|
name: Optional[str] = None,
|
1010
1196
|
overwrite: bool = False,
|
1011
|
-
client: "PrefectClient" = None,
|
1197
|
+
client: Optional["PrefectClient"] = None,
|
1012
1198
|
):
|
1013
1199
|
"""
|
1014
1200
|
Saves the values of a block as a block document.
|
@@ -1030,25 +1216,12 @@ class Block(BaseModel, ABC):
|
|
1030
1216
|
async def delete(
|
1031
1217
|
cls,
|
1032
1218
|
name: str,
|
1033
|
-
client: "PrefectClient" = None,
|
1219
|
+
client: Optional["PrefectClient"] = None,
|
1034
1220
|
):
|
1035
1221
|
block_document, block_document_name = await cls._get_block_document(name)
|
1036
1222
|
|
1037
1223
|
await client.delete_block_document(block_document.id)
|
1038
1224
|
|
1039
|
-
def _iter(self, *, include=None, exclude=None, **kwargs):
|
1040
|
-
# Injects the `block_type_slug` into serialized payloads for dispatch
|
1041
|
-
for key_value in super()._iter(include=include, exclude=exclude, **kwargs):
|
1042
|
-
yield key_value
|
1043
|
-
|
1044
|
-
# Respect inclusion and exclusion still
|
1045
|
-
if include and "block_type_slug" not in include:
|
1046
|
-
return
|
1047
|
-
if exclude and "block_type_slug" in exclude:
|
1048
|
-
return
|
1049
|
-
|
1050
|
-
yield "block_type_slug", self.get_block_type_slug()
|
1051
|
-
|
1052
1225
|
def __new__(cls: Type[Self], **kwargs) -> Self:
|
1053
1226
|
"""
|
1054
1227
|
Create an instance of the Block subclass type if a `block_type_slug` is
|
@@ -1057,21 +1230,9 @@ class Block(BaseModel, ABC):
|
|
1057
1230
|
block_type_slug = kwargs.pop("block_type_slug", None)
|
1058
1231
|
if block_type_slug:
|
1059
1232
|
subcls = lookup_type(cls, dispatch_key=block_type_slug)
|
1060
|
-
|
1061
|
-
# NOTE: This is a workaround for an obscure issue where copied models were
|
1062
|
-
# missing attributes. This pattern is from Pydantic's
|
1063
|
-
# `BaseModel._copy_and_set_values`.
|
1064
|
-
# The issue this fixes could not be reproduced in unit tests that
|
1065
|
-
# directly targeted dispatch handling and was only observed when
|
1066
|
-
# copying then saving infrastructure blocks on deployment models.
|
1067
|
-
object.__setattr__(m, "__dict__", kwargs)
|
1068
|
-
object.__setattr__(m, "__fields_set__", set(kwargs.keys()))
|
1069
|
-
return m
|
1233
|
+
return super().__new__(subcls)
|
1070
1234
|
else:
|
1071
|
-
|
1072
|
-
object.__setattr__(m, "__dict__", kwargs)
|
1073
|
-
object.__setattr__(m, "__fields_set__", set(kwargs.keys()))
|
1074
|
-
return m
|
1235
|
+
return super().__new__(cls)
|
1075
1236
|
|
1076
1237
|
def get_block_placeholder(self) -> str:
|
1077
1238
|
"""
|
@@ -1094,3 +1255,94 @@ class Block(BaseModel, ABC):
|
|
1094
1255
|
)
|
1095
1256
|
|
1096
1257
|
return f"prefect.blocks.{self.get_block_type_slug()}.{block_document_name}"
|
1258
|
+
|
1259
|
+
@classmethod
|
1260
|
+
def model_json_schema(
|
1261
|
+
cls,
|
1262
|
+
by_alias: bool = True,
|
1263
|
+
ref_template: str = "#/definitions/{model}",
|
1264
|
+
schema_generator: Type[GenerateJsonSchema] = GenerateJsonSchema,
|
1265
|
+
mode: Literal["validation", "serialization"] = "validation",
|
1266
|
+
) -> Dict[str, Any]:
|
1267
|
+
"""TODO: stop overriding this method - use GenerateSchema in ConfigDict instead?"""
|
1268
|
+
schema = super().model_json_schema(
|
1269
|
+
by_alias, ref_template, schema_generator, mode
|
1270
|
+
)
|
1271
|
+
|
1272
|
+
# ensure backwards compatibility by copying $defs into definitions
|
1273
|
+
if "$defs" in schema:
|
1274
|
+
schema["definitions"] = schema.pop("$defs")
|
1275
|
+
|
1276
|
+
# we aren't expecting these additional fields in the schema
|
1277
|
+
if "additionalProperties" in schema:
|
1278
|
+
schema.pop("additionalProperties")
|
1279
|
+
|
1280
|
+
for _, definition in schema.get("definitions", {}).items():
|
1281
|
+
if "additionalProperties" in definition:
|
1282
|
+
definition.pop("additionalProperties")
|
1283
|
+
|
1284
|
+
return schema
|
1285
|
+
|
1286
|
+
@classmethod
|
1287
|
+
def model_validate(
|
1288
|
+
cls: type[Self],
|
1289
|
+
obj: Any,
|
1290
|
+
*,
|
1291
|
+
strict: Optional[bool] = None,
|
1292
|
+
from_attributes: Optional[bool] = None,
|
1293
|
+
context: Optional[Dict[str, Any]] = None,
|
1294
|
+
) -> Self:
|
1295
|
+
if isinstance(obj, dict):
|
1296
|
+
extra_serializer_fields = {
|
1297
|
+
"_block_document_id",
|
1298
|
+
"_block_document_name",
|
1299
|
+
"_is_anonymous",
|
1300
|
+
}.intersection(obj.keys())
|
1301
|
+
for field in extra_serializer_fields:
|
1302
|
+
obj.pop(field, None)
|
1303
|
+
|
1304
|
+
return super().model_validate(
|
1305
|
+
obj, strict=strict, from_attributes=from_attributes, context=context
|
1306
|
+
)
|
1307
|
+
|
1308
|
+
def model_dump(
|
1309
|
+
self,
|
1310
|
+
*,
|
1311
|
+
mode: Union[Literal["json", "python"], str] = "python",
|
1312
|
+
include: "IncEx" = None,
|
1313
|
+
exclude: "IncEx" = None,
|
1314
|
+
context: Optional[Dict[str, Any]] = None,
|
1315
|
+
by_alias: bool = False,
|
1316
|
+
exclude_unset: bool = False,
|
1317
|
+
exclude_defaults: bool = False,
|
1318
|
+
exclude_none: bool = False,
|
1319
|
+
round_trip: bool = False,
|
1320
|
+
warnings: Union[bool, Literal["none", "warn", "error"]] = True,
|
1321
|
+
serialize_as_any: bool = False,
|
1322
|
+
) -> Dict[str, Any]:
|
1323
|
+
d = super().model_dump(
|
1324
|
+
mode=mode,
|
1325
|
+
include=include,
|
1326
|
+
exclude=exclude,
|
1327
|
+
context=context,
|
1328
|
+
by_alias=by_alias,
|
1329
|
+
exclude_unset=exclude_unset,
|
1330
|
+
exclude_defaults=exclude_defaults,
|
1331
|
+
exclude_none=exclude_none,
|
1332
|
+
round_trip=round_trip,
|
1333
|
+
warnings=warnings,
|
1334
|
+
serialize_as_any=serialize_as_any,
|
1335
|
+
)
|
1336
|
+
|
1337
|
+
extra_serializer_fields = {
|
1338
|
+
"block_type_slug",
|
1339
|
+
"_block_document_id",
|
1340
|
+
"_block_document_name",
|
1341
|
+
"_is_anonymous",
|
1342
|
+
}.intersection(d.keys())
|
1343
|
+
|
1344
|
+
for field in extra_serializer_fields:
|
1345
|
+
if (include and field not in include) or (exclude and field in exclude):
|
1346
|
+
d.pop(field)
|
1347
|
+
|
1348
|
+
return d
|