prefect-client 2.20.2__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 +423 -164
- 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 +667 -440
- 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 -2466
- 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 +124 -51
- 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 +138 -48
- 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.2.dist-info → prefect_client-3.0.0.dist-info}/METADATA +30 -26
- prefect_client-3.0.0.dist-info/RECORD +201 -0
- {prefect_client-2.20.2.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.2.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.2.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
- {prefect_client-2.20.2.dist-info → prefect_client-3.0.0.dist-info}/top_level.txt +0 -0
prefect/blocks/core.py
CHANGED
@@ -2,39 +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
|
-
from griffe
|
22
|
-
from griffe.docstrings.dataclasses import DocstringSection, DocstringSectionKind
|
23
|
-
from griffe.docstrings.parsers import Parser, parse
|
27
|
+
from griffe import Docstring, DocstringSection, DocstringSectionKind, Parser, parse
|
24
28
|
from packaging.version import InvalidVersion, Version
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
34
42
|
|
35
43
|
import prefect
|
36
44
|
import prefect.exceptions
|
37
|
-
from prefect.blocks.fields import SecretDict
|
38
45
|
from prefect.client.schemas import (
|
39
46
|
DEFAULT_BLOCK_SCHEMA_VERSION,
|
40
47
|
BlockDocument,
|
@@ -43,26 +50,27 @@ from prefect.client.schemas import (
|
|
43
50
|
BlockTypeUpdate,
|
44
51
|
)
|
45
52
|
from prefect.client.utilities import inject_client
|
46
|
-
from prefect.events
|
47
|
-
ResourceTuple,
|
48
|
-
emit_instance_method_called_event,
|
49
|
-
instrument_instance_method_call,
|
50
|
-
instrument_method_calls_on_class_instances,
|
51
|
-
)
|
53
|
+
from prefect.events import emit_event
|
52
54
|
from prefect.logging.loggers import disable_logger
|
55
|
+
from prefect.types import SecretDict
|
53
56
|
from prefect.utilities.asyncutils import sync_compatible
|
54
|
-
from prefect.utilities.collections import listrepr, remove_nested_keys
|
57
|
+
from prefect.utilities.collections import listrepr, remove_nested_keys, visit_collection
|
55
58
|
from prefect.utilities.dispatch import lookup_type, register_base_type
|
56
59
|
from prefect.utilities.hashing import hash_objects
|
57
60
|
from prefect.utilities.importtools import to_qualified_name
|
61
|
+
from prefect.utilities.pydantic import handle_secret_render
|
58
62
|
from prefect.utilities.slugify import slugify
|
59
63
|
|
60
64
|
if TYPE_CHECKING:
|
65
|
+
from pydantic.main import IncEx
|
66
|
+
|
61
67
|
from prefect.client.orchestration import PrefectClient
|
62
68
|
|
63
69
|
R = TypeVar("R")
|
64
70
|
P = ParamSpec("P")
|
65
71
|
|
72
|
+
ResourceTuple = Tuple[Dict[str, Any], List[Dict[str, Any]]]
|
73
|
+
|
66
74
|
|
67
75
|
def block_schema_to_key(schema: BlockSchema) -> str:
|
68
76
|
"""
|
@@ -120,32 +128,42 @@ def _is_subclass(cls, parent_cls) -> bool:
|
|
120
128
|
Checks if a given class is a subclass of another class. Unlike issubclass,
|
121
129
|
this will not throw an exception if cls is an instance instead of a type.
|
122
130
|
"""
|
123
|
-
|
131
|
+
# For python<=3.11 inspect.isclass() will return True for parametrized types (e.g. list[str])
|
132
|
+
# so we need to check for get_origin() to avoid TypeError for issubclass.
|
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
|
-
secrets list, supporting nested Union /
|
130
|
-
mutates the input secrets list, thus does not return anything.
|
141
|
+
secrets list, supporting nested Union / Dict / Tuple / List / BaseModel fields.
|
142
|
+
Also, note, this function mutates the input secrets list, thus does not return anything.
|
131
143
|
"""
|
132
|
-
if get_origin(type_)
|
133
|
-
for
|
134
|
-
_collect_secret_fields(name,
|
144
|
+
if get_origin(type_) in (Union, dict, list, tuple):
|
145
|
+
for nested_type in get_args(type_):
|
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,56 +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
|
-
for field in model.__fields__.values():
|
245
|
-
if Block.is_block_class(field.type_):
|
246
|
-
refs[field.name] = field.type_._to_block_schema_reference_dict()
|
247
|
-
if get_origin(field.type_) is Union:
|
248
|
-
for type_ in get_args(field.type_):
|
249
|
-
if Block.is_block_class(type_):
|
250
|
-
if isinstance(refs.get(field.name), list):
|
251
|
-
refs[field.name].append(
|
252
|
-
type_._to_block_schema_reference_dict()
|
253
|
-
)
|
254
|
-
elif isinstance(refs.get(field.name), dict):
|
255
|
-
refs[field.name] = [
|
256
|
-
refs[field.name],
|
257
|
-
type_._to_block_schema_reference_dict(),
|
258
|
-
]
|
259
|
-
else:
|
260
|
-
refs[
|
261
|
-
field.name
|
262
|
-
] = type_._to_block_schema_reference_dict()
|
278
|
+
model_config = ConfigDict(
|
279
|
+
extra="allow",
|
280
|
+
json_schema_extra=schema_extra,
|
281
|
+
)
|
263
282
|
|
264
283
|
def __init__(self, *args, **kwargs):
|
265
284
|
super().__init__(*args, **kwargs)
|
@@ -270,7 +289,7 @@ class Block(BaseModel, ABC):
|
|
270
289
|
|
271
290
|
def __repr_args__(self):
|
272
291
|
repr_args = super().__repr_args__()
|
273
|
-
data_keys = self.
|
292
|
+
data_keys = self.model_json_schema()["properties"].keys()
|
274
293
|
return [
|
275
294
|
(key, value) for key, value in repr_args if key is None or key in data_keys
|
276
295
|
]
|
@@ -284,25 +303,26 @@ class Block(BaseModel, ABC):
|
|
284
303
|
# Attribute to customize the name of the block type created
|
285
304
|
# when the block is registered with the API. If not set, block
|
286
305
|
# type name will default to the class name.
|
287
|
-
_block_type_name: Optional[str] = None
|
288
|
-
_block_type_slug: Optional[str] = None
|
306
|
+
_block_type_name: ClassVar[Optional[str]] = None
|
307
|
+
_block_type_slug: ClassVar[Optional[str]] = None
|
289
308
|
|
290
309
|
# Attributes used to set properties on a block type when registered
|
291
310
|
# with the API.
|
292
|
-
_logo_url: Optional[HttpUrl] = None
|
293
|
-
_documentation_url: Optional[HttpUrl] = None
|
294
|
-
_description: Optional[str] = None
|
295
|
-
_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
|
296
319
|
|
297
320
|
# -- private instance variables
|
298
321
|
# these are set when blocks are loaded from the API
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
_block_document_id: Optional[UUID] = None
|
304
|
-
_block_document_name: Optional[str] = None
|
305
|
-
_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)
|
306
326
|
|
307
327
|
# Exclude `save` as it uses the `sync_compatible` decorator and needs to be
|
308
328
|
# decorated directly.
|
@@ -314,6 +334,31 @@ class Block(BaseModel, ABC):
|
|
314
334
|
return None # The base class is abstract
|
315
335
|
return block_schema_to_key(cls._to_block_schema())
|
316
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
|
+
|
317
362
|
@classmethod
|
318
363
|
def get_block_type_name(cls):
|
319
364
|
return cls._block_type_name or cls.__name__
|
@@ -379,7 +424,9 @@ class Block(BaseModel, ABC):
|
|
379
424
|
str: The calculated checksum prefixed with the hashing algorithm used.
|
380
425
|
"""
|
381
426
|
block_schema_fields = (
|
382
|
-
cls.
|
427
|
+
cls.model_json_schema()
|
428
|
+
if block_schema_fields is None
|
429
|
+
else block_schema_fields
|
383
430
|
)
|
384
431
|
fields_for_checksum = remove_nested_keys(["secret_fields"], block_schema_fields)
|
385
432
|
if fields_for_checksum.get("definitions"):
|
@@ -404,6 +451,7 @@ class Block(BaseModel, ABC):
|
|
404
451
|
block_schema_id: Optional[UUID] = None,
|
405
452
|
block_type_id: Optional[UUID] = None,
|
406
453
|
is_anonymous: Optional[bool] = None,
|
454
|
+
include_secrets: bool = False,
|
407
455
|
) -> BlockDocument:
|
408
456
|
"""
|
409
457
|
Creates the corresponding block document based on the data stored in a block.
|
@@ -441,10 +489,14 @@ class Block(BaseModel, ABC):
|
|
441
489
|
# The keys passed to `include` must NOT be aliases, else some items will be missed
|
442
490
|
# i.e. must do `self.schema_` vs `self.schema` to get a `schema_ = Field(alias="schema")`
|
443
491
|
# reported from https://github.com/PrefectHQ/prefect-dbt/issues/54
|
444
|
-
data_keys = self.
|
492
|
+
data_keys = self.model_json_schema(by_alias=False)["properties"].keys()
|
445
493
|
|
446
494
|
# `block_document_data`` must return the aliased version for it to show in the UI
|
447
|
-
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
|
+
)
|
448
500
|
|
449
501
|
# Iterate through and find blocks that already have saved block documents to
|
450
502
|
# create references to those saved block documents.
|
@@ -484,7 +536,7 @@ class Block(BaseModel, ABC):
|
|
484
536
|
Returns:
|
485
537
|
BlockSchema: The corresponding block schema.
|
486
538
|
"""
|
487
|
-
fields = cls.
|
539
|
+
fields = cls.model_json_schema()
|
488
540
|
return BlockSchema(
|
489
541
|
id=cls._block_schema_id if cls._block_schema_id is not None else uuid4(),
|
490
542
|
checksum=cls._calculate_schema_checksum(),
|
@@ -539,7 +591,7 @@ class Block(BaseModel, ABC):
|
|
539
591
|
code example from the class docstring if an override is not provided.
|
540
592
|
"""
|
541
593
|
code_example = (
|
542
|
-
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
|
543
595
|
)
|
544
596
|
# If no code example override has been provided, attempt to find a examples
|
545
597
|
# section or an admonition with the annotation "example" and use that as the
|
@@ -606,7 +658,7 @@ class Block(BaseModel, ABC):
|
|
606
658
|
)
|
607
659
|
|
608
660
|
@classmethod
|
609
|
-
def _from_block_document(cls, block_document: BlockDocument):
|
661
|
+
def _from_block_document(cls, block_document: BlockDocument) -> Self:
|
610
662
|
"""
|
611
663
|
Instantiates a block from a given block document. The corresponding block class
|
612
664
|
will be looked up in the block registry based on the corresponding block schema
|
@@ -634,9 +686,7 @@ class Block(BaseModel, ABC):
|
|
634
686
|
else cls.get_block_class_from_schema(block_document.block_schema)
|
635
687
|
)
|
636
688
|
|
637
|
-
|
638
|
-
|
639
|
-
block = block_cls.parse_obj(block_document.data)
|
689
|
+
block = block_cls.model_validate(block_document.data)
|
640
690
|
block._block_document_id = block_document.id
|
641
691
|
block.__class__._block_schema_id = block_document.block_schema_id
|
642
692
|
block.__class__._block_type_id = block_document.block_type_id
|
@@ -646,13 +696,11 @@ class Block(BaseModel, ABC):
|
|
646
696
|
block_document.block_document_references
|
647
697
|
)
|
648
698
|
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
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)
|
656
704
|
|
657
705
|
return block
|
658
706
|
|
@@ -731,7 +779,7 @@ class Block(BaseModel, ABC):
|
|
731
779
|
async def _get_block_document(
|
732
780
|
cls,
|
733
781
|
name: str,
|
734
|
-
client: "PrefectClient" = None,
|
782
|
+
client: Optional["PrefectClient"] = None,
|
735
783
|
):
|
736
784
|
if cls.__name__ == "Block":
|
737
785
|
block_type_slug, block_document_name = name.split("/", 1)
|
@@ -751,6 +799,33 @@ class Block(BaseModel, ABC):
|
|
751
799
|
|
752
800
|
return block_document, block_document_name
|
753
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
|
+
|
754
829
|
@classmethod
|
755
830
|
@sync_compatible
|
756
831
|
@inject_client
|
@@ -758,8 +833,8 @@ class Block(BaseModel, ABC):
|
|
758
833
|
cls,
|
759
834
|
name: str,
|
760
835
|
validate: bool = True,
|
761
|
-
client: "PrefectClient" = None,
|
762
|
-
):
|
836
|
+
client: Optional["PrefectClient"] = None,
|
837
|
+
) -> "Self":
|
763
838
|
"""
|
764
839
|
Retrieves data from the block document with the given name for the block type
|
765
840
|
that corresponds with the current class and returns an instantiated version of
|
@@ -835,8 +910,108 @@ class Block(BaseModel, ABC):
|
|
835
910
|
loaded_block.save("my-custom-message", overwrite=True)
|
836
911
|
```
|
837
912
|
"""
|
838
|
-
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`.
|
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.
|
839
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
|
+
"""
|
840
1015
|
try:
|
841
1016
|
return cls._from_block_document(block_document)
|
842
1017
|
except ValidationError as e:
|
@@ -844,19 +1019,19 @@ class Block(BaseModel, ABC):
|
|
844
1019
|
missing_fields = tuple(err["loc"][0] for err in e.errors())
|
845
1020
|
missing_block_data = {field: None for field in missing_fields}
|
846
1021
|
warnings.warn(
|
847
|
-
f"Could not fully load {
|
848
|
-
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"
|
849
1024
|
" required fields were added to the schema for"
|
850
1025
|
f" {cls.__name__!r} that did not exist on the class when this block"
|
851
1026
|
" was last saved. Please specify values for new field(s):"
|
852
1027
|
f" {listrepr(missing_fields)}, then run"
|
853
|
-
f' `{cls.__name__}.save("{
|
1028
|
+
f' `{cls.__name__}.save("{block_document.name}", overwrite=True)`,'
|
854
1029
|
" and load this block again before attempting to use it."
|
855
1030
|
)
|
856
|
-
return cls.
|
1031
|
+
return cls.model_construct(**block_document.data, **missing_block_data)
|
857
1032
|
raise RuntimeError(
|
858
|
-
f"Unable to load {
|
859
|
-
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"
|
860
1035
|
" validation, try loading again with `validate=False`."
|
861
1036
|
) from e
|
862
1037
|
|
@@ -864,10 +1039,22 @@ class Block(BaseModel, ABC):
|
|
864
1039
|
def is_block_class(block) -> bool:
|
865
1040
|
return _is_subclass(block, Block)
|
866
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
|
+
|
867
1054
|
@classmethod
|
868
1055
|
@sync_compatible
|
869
1056
|
@inject_client
|
870
|
-
async def register_type_and_schema(cls, client: "PrefectClient" = None):
|
1057
|
+
async def register_type_and_schema(cls, client: Optional["PrefectClient"] = None):
|
871
1058
|
"""
|
872
1059
|
Makes block available for configuration with current Prefect API.
|
873
1060
|
Recursively registers all nested blocks. Registration is idempotent.
|
@@ -888,19 +1075,24 @@ class Block(BaseModel, ABC):
|
|
888
1075
|
"subclass and not on a Block interface class directly."
|
889
1076
|
)
|
890
1077
|
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
1078
|
+
async def register_blocks_in_annotation(annotation: type) -> None:
|
1079
|
+
"""Walk through the annotation and register any nested blocks."""
|
1080
|
+
if Block.is_block_class(annotation):
|
1081
|
+
await annotation.register_type_and_schema(client=client)
|
1082
|
+
elif get_origin(annotation) in (Union, tuple, list, dict):
|
1083
|
+
for inner_annotation in get_args(annotation):
|
1084
|
+
await register_blocks_in_annotation(inner_annotation)
|
1085
|
+
|
1086
|
+
for field in cls.model_fields.values():
|
1087
|
+
await register_blocks_in_annotation(field.annotation)
|
898
1088
|
|
899
1089
|
try:
|
900
1090
|
block_type = await client.read_block_type_by_slug(
|
901
1091
|
slug=cls.get_block_type_slug()
|
902
1092
|
)
|
1093
|
+
|
903
1094
|
cls._block_type_id = block_type.id
|
1095
|
+
|
904
1096
|
local_block_type = cls._to_block_type()
|
905
1097
|
if _should_update_block_type(
|
906
1098
|
local_block_type=local_block_type, server_block_type=block_type
|
@@ -930,7 +1122,7 @@ class Block(BaseModel, ABC):
|
|
930
1122
|
name: Optional[str] = None,
|
931
1123
|
is_anonymous: bool = False,
|
932
1124
|
overwrite: bool = False,
|
933
|
-
client: "PrefectClient" = None,
|
1125
|
+
client: Optional["PrefectClient"] = None,
|
934
1126
|
):
|
935
1127
|
"""
|
936
1128
|
Saves the values of a block as a block document with an option to save as an
|
@@ -978,7 +1170,9 @@ class Block(BaseModel, ABC):
|
|
978
1170
|
block_document_id = existing_block_document.id
|
979
1171
|
await client.update_block_document(
|
980
1172
|
block_document_id=block_document_id,
|
981
|
-
block_document=self._to_block_document(
|
1173
|
+
block_document=self._to_block_document(
|
1174
|
+
name=name, include_secrets=True
|
1175
|
+
),
|
982
1176
|
)
|
983
1177
|
block_document = await client.read_block_document(
|
984
1178
|
block_document_id=block_document_id
|
@@ -996,12 +1190,11 @@ class Block(BaseModel, ABC):
|
|
996
1190
|
return self._block_document_id
|
997
1191
|
|
998
1192
|
@sync_compatible
|
999
|
-
@instrument_instance_method_call
|
1000
1193
|
async def save(
|
1001
1194
|
self,
|
1002
1195
|
name: Optional[str] = None,
|
1003
1196
|
overwrite: bool = False,
|
1004
|
-
client: "PrefectClient" = None,
|
1197
|
+
client: Optional["PrefectClient"] = None,
|
1005
1198
|
):
|
1006
1199
|
"""
|
1007
1200
|
Saves the values of a block as a block document.
|
@@ -1023,25 +1216,12 @@ class Block(BaseModel, ABC):
|
|
1023
1216
|
async def delete(
|
1024
1217
|
cls,
|
1025
1218
|
name: str,
|
1026
|
-
client: "PrefectClient" = None,
|
1219
|
+
client: Optional["PrefectClient"] = None,
|
1027
1220
|
):
|
1028
1221
|
block_document, block_document_name = await cls._get_block_document(name)
|
1029
1222
|
|
1030
1223
|
await client.delete_block_document(block_document.id)
|
1031
1224
|
|
1032
|
-
def _iter(self, *, include=None, exclude=None, **kwargs):
|
1033
|
-
# Injects the `block_type_slug` into serialized payloads for dispatch
|
1034
|
-
for key_value in super()._iter(include=include, exclude=exclude, **kwargs):
|
1035
|
-
yield key_value
|
1036
|
-
|
1037
|
-
# Respect inclusion and exclusion still
|
1038
|
-
if include and "block_type_slug" not in include:
|
1039
|
-
return
|
1040
|
-
if exclude and "block_type_slug" in exclude:
|
1041
|
-
return
|
1042
|
-
|
1043
|
-
yield "block_type_slug", self.get_block_type_slug()
|
1044
|
-
|
1045
1225
|
def __new__(cls: Type[Self], **kwargs) -> Self:
|
1046
1226
|
"""
|
1047
1227
|
Create an instance of the Block subclass type if a `block_type_slug` is
|
@@ -1050,21 +1230,9 @@ class Block(BaseModel, ABC):
|
|
1050
1230
|
block_type_slug = kwargs.pop("block_type_slug", None)
|
1051
1231
|
if block_type_slug:
|
1052
1232
|
subcls = lookup_type(cls, dispatch_key=block_type_slug)
|
1053
|
-
|
1054
|
-
# NOTE: This is a workaround for an obscure issue where copied models were
|
1055
|
-
# missing attributes. This pattern is from Pydantic's
|
1056
|
-
# `BaseModel._copy_and_set_values`.
|
1057
|
-
# The issue this fixes could not be reproduced in unit tests that
|
1058
|
-
# directly targeted dispatch handling and was only observed when
|
1059
|
-
# copying then saving infrastructure blocks on deployment models.
|
1060
|
-
object.__setattr__(m, "__dict__", kwargs)
|
1061
|
-
object.__setattr__(m, "__fields_set__", set(kwargs.keys()))
|
1062
|
-
return m
|
1233
|
+
return super().__new__(subcls)
|
1063
1234
|
else:
|
1064
|
-
|
1065
|
-
object.__setattr__(m, "__dict__", kwargs)
|
1066
|
-
object.__setattr__(m, "__fields_set__", set(kwargs.keys()))
|
1067
|
-
return m
|
1235
|
+
return super().__new__(cls)
|
1068
1236
|
|
1069
1237
|
def get_block_placeholder(self) -> str:
|
1070
1238
|
"""
|
@@ -1087,3 +1255,94 @@ class Block(BaseModel, ABC):
|
|
1087
1255
|
)
|
1088
1256
|
|
1089
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
|