prefect-client 3.0.0rc20__py3-none-any.whl → 3.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prefect/_internal/compatibility/deprecated.py +1 -1
- prefect/_internal/compatibility/migration.py +1 -1
- prefect/artifacts.py +1 -1
- prefect/blocks/core.py +3 -4
- prefect/blocks/notifications.py +31 -10
- prefect/blocks/system.py +4 -4
- prefect/blocks/webhook.py +11 -1
- prefect/client/cloud.py +2 -1
- prefect/client/orchestration.py +93 -21
- prefect/client/schemas/actions.py +2 -2
- prefect/client/schemas/objects.py +24 -6
- prefect/client/types/flexible_schedule_list.py +1 -1
- prefect/concurrency/asyncio.py +45 -6
- prefect/concurrency/services.py +1 -1
- prefect/concurrency/sync.py +21 -27
- prefect/concurrency/v1/asyncio.py +3 -0
- prefect/concurrency/v1/sync.py +4 -5
- prefect/context.py +11 -9
- prefect/deployments/runner.py +4 -3
- prefect/events/actions.py +6 -0
- prefect/exceptions.py +6 -0
- prefect/filesystems.py +5 -3
- prefect/flow_engine.py +22 -11
- prefect/flows.py +0 -2
- prefect/futures.py +2 -1
- prefect/locking/__init__.py +0 -0
- prefect/locking/filesystem.py +243 -0
- prefect/locking/memory.py +213 -0
- prefect/locking/protocol.py +122 -0
- prefect/logging/handlers.py +0 -2
- prefect/logging/loggers.py +0 -18
- prefect/logging/logging.yml +1 -0
- prefect/main.py +19 -5
- prefect/records/base.py +12 -0
- prefect/records/filesystem.py +10 -4
- prefect/records/memory.py +6 -0
- prefect/records/result_store.py +18 -6
- prefect/results.py +702 -205
- prefect/runner/runner.py +74 -5
- prefect/settings.py +11 -4
- prefect/states.py +40 -23
- prefect/task_engine.py +39 -37
- prefect/task_worker.py +6 -4
- prefect/tasks.py +24 -6
- prefect/transactions.py +116 -54
- prefect/utilities/callables.py +1 -3
- prefect/utilities/engine.py +16 -8
- prefect/utilities/importtools.py +1 -0
- prefect/utilities/urls.py +70 -12
- prefect/variables.py +34 -24
- prefect/workers/base.py +14 -6
- prefect/workers/process.py +1 -3
- {prefect_client-3.0.0rc20.dist-info → prefect_client-3.0.2.dist-info}/METADATA +2 -2
- {prefect_client-3.0.0rc20.dist-info → prefect_client-3.0.2.dist-info}/RECORD +57 -53
- {prefect_client-3.0.0rc20.dist-info → prefect_client-3.0.2.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.0rc20.dist-info → prefect_client-3.0.2.dist-info}/WHEEL +0 -0
- {prefect_client-3.0.0rc20.dist-info → prefect_client-3.0.2.dist-info}/top_level.txt +0 -0
@@ -30,7 +30,7 @@ M = TypeVar("M", bound=BaseModel)
|
|
30
30
|
|
31
31
|
|
32
32
|
DEPRECATED_WARNING = (
|
33
|
-
"{name} has been deprecated{when}. It will not be available after {end_date}."
|
33
|
+
"{name} has been deprecated{when}. It will not be available in new releases after {end_date}."
|
34
34
|
" {help}"
|
35
35
|
)
|
36
36
|
DEPRECATED_MOVED_WARNING = (
|
@@ -60,7 +60,7 @@ MOVED_IN_V3 = {
|
|
60
60
|
"prefect.client:get_client": "prefect.client.orchestration:get_client",
|
61
61
|
}
|
62
62
|
|
63
|
-
upgrade_guide_msg = "Refer to the upgrade guide for more information: https://docs.prefect.io/latest/
|
63
|
+
upgrade_guide_msg = "Refer to the upgrade guide for more information: https://docs.prefect.io/latest/resources/upgrade-agents-to-workers."
|
64
64
|
|
65
65
|
REMOVED_IN_V3 = {
|
66
66
|
"prefect.client.schemas.objects:MinimalDeploymentSchedule": "Use `prefect.client.schemas.actions.DeploymentScheduleCreate` instead.",
|
prefect/artifacts.py
CHANGED
@@ -31,7 +31,7 @@ if TYPE_CHECKING:
|
|
31
31
|
class Artifact(ArtifactRequest):
|
32
32
|
"""
|
33
33
|
An artifact is a piece of data that is created by a flow or task run.
|
34
|
-
https://docs.prefect.io/latest/
|
34
|
+
https://docs.prefect.io/latest/develop/artifacts
|
35
35
|
|
36
36
|
Arguments:
|
37
37
|
type: A string identifying the type of artifact.
|
prefect/blocks/core.py
CHANGED
@@ -555,10 +555,9 @@ class Block(BaseModel, ABC):
|
|
555
555
|
`<module>:11: No type or annotation for parameter 'write_json'`
|
556
556
|
because griffe is unable to parse the types from pydantic.BaseModel.
|
557
557
|
"""
|
558
|
-
with disable_logger("griffe
|
559
|
-
|
560
|
-
|
561
|
-
parsed = parse(docstring, Parser.google)
|
558
|
+
with disable_logger("griffe"):
|
559
|
+
docstring = Docstring(cls.__doc__)
|
560
|
+
parsed = parse(docstring, Parser.google)
|
562
561
|
return parsed
|
563
562
|
|
564
563
|
@classmethod
|
prefect/blocks/notifications.py
CHANGED
@@ -10,6 +10,7 @@ from prefect.logging import LogEavesdropper
|
|
10
10
|
from prefect.types import SecretDict
|
11
11
|
from prefect.utilities.asyncutils import sync_compatible
|
12
12
|
from prefect.utilities.templating import apply_values, find_placeholders
|
13
|
+
from prefect.utilities.urls import validate_restricted_url
|
13
14
|
|
14
15
|
PREFECT_NOTIFY_TYPE_DEFAULT = "prefect_default"
|
15
16
|
|
@@ -73,13 +74,33 @@ class AppriseNotificationBlock(AbstractAppriseNotificationBlock, ABC):
|
|
73
74
|
A base class for sending notifications using Apprise, through webhook URLs.
|
74
75
|
"""
|
75
76
|
|
76
|
-
_documentation_url = "https://docs.prefect.io/
|
77
|
+
_documentation_url = "https://docs.prefect.io/latest/automate/events/automations-triggers#sending-notifications-with-automations"
|
77
78
|
url: SecretStr = Field(
|
78
79
|
default=...,
|
79
80
|
title="Webhook URL",
|
80
81
|
description="Incoming webhook URL used to send notifications.",
|
81
82
|
examples=["https://hooks.example.com/XXX"],
|
82
83
|
)
|
84
|
+
allow_private_urls: bool = Field(
|
85
|
+
default=True,
|
86
|
+
description="Whether to allow notifications to private URLs. Defaults to True.",
|
87
|
+
)
|
88
|
+
|
89
|
+
@sync_compatible
|
90
|
+
async def notify(
|
91
|
+
self,
|
92
|
+
body: str,
|
93
|
+
subject: Optional[str] = None,
|
94
|
+
):
|
95
|
+
if not self.allow_private_urls:
|
96
|
+
try:
|
97
|
+
validate_restricted_url(self.url.get_secret_value())
|
98
|
+
except ValueError as exc:
|
99
|
+
if self._raise_on_failure:
|
100
|
+
raise NotificationError(str(exc))
|
101
|
+
raise
|
102
|
+
|
103
|
+
await super().notify(body, subject)
|
83
104
|
|
84
105
|
|
85
106
|
# TODO: Move to prefect-slack once collection block auto-registration is
|
@@ -100,7 +121,7 @@ class SlackWebhook(AppriseNotificationBlock):
|
|
100
121
|
|
101
122
|
_block_type_name = "Slack Webhook"
|
102
123
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/c1965ecbf8704ee1ea20d77786de9a41ce1087d1-500x500.png"
|
103
|
-
_documentation_url = "https://docs.prefect.io/
|
124
|
+
_documentation_url = "https://docs.prefect.io/latest/automate/events/automations-triggers#sending-notifications-with-automations"
|
104
125
|
|
105
126
|
url: SecretStr = Field(
|
106
127
|
default=...,
|
@@ -126,7 +147,7 @@ class MicrosoftTeamsWebhook(AppriseNotificationBlock):
|
|
126
147
|
_block_type_name = "Microsoft Teams Webhook"
|
127
148
|
_block_type_slug = "ms-teams-webhook"
|
128
149
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/817efe008a57f0a24f3587414714b563e5e23658-250x250.png"
|
129
|
-
_documentation_url = "https://docs.prefect.io/
|
150
|
+
_documentation_url = "https://docs.prefect.io/latest/automate/events/automations-triggers#sending-notifications-with-automations"
|
130
151
|
|
131
152
|
url: SecretStr = Field(
|
132
153
|
default=...,
|
@@ -181,7 +202,7 @@ class PagerDutyWebHook(AbstractAppriseNotificationBlock):
|
|
181
202
|
_block_type_name = "Pager Duty Webhook"
|
182
203
|
_block_type_slug = "pager-duty-webhook"
|
183
204
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/8dbf37d17089c1ce531708eac2e510801f7b3aee-250x250.png"
|
184
|
-
_documentation_url = "https://docs.prefect.io/
|
205
|
+
_documentation_url = "https://docs.prefect.io/latest/automate/events/automations-triggers#sending-notifications-with-automations"
|
185
206
|
|
186
207
|
# The default cannot be prefect_default because NotifyPagerDuty's
|
187
208
|
# PAGERDUTY_SEVERITY_MAP only has these notify types defined as keys
|
@@ -291,7 +312,7 @@ class TwilioSMS(AbstractAppriseNotificationBlock):
|
|
291
312
|
_block_type_name = "Twilio SMS"
|
292
313
|
_block_type_slug = "twilio-sms"
|
293
314
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/8bd8777999f82112c09b9c8d57083ac75a4a0d65-250x250.png" # noqa
|
294
|
-
_documentation_url = "https://docs.prefect.io/
|
315
|
+
_documentation_url = "https://docs.prefect.io/latest/automate/events/automations-triggers#sending-notifications-with-automations"
|
295
316
|
|
296
317
|
account_sid: str = Field(
|
297
318
|
default=...,
|
@@ -360,7 +381,7 @@ class OpsgenieWebhook(AbstractAppriseNotificationBlock):
|
|
360
381
|
_block_type_name = "Opsgenie Webhook"
|
361
382
|
_block_type_slug = "opsgenie-webhook"
|
362
383
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/d8b5bc6244ae6cd83b62ec42f10d96e14d6e9113-280x280.png"
|
363
|
-
_documentation_url = "https://docs.prefect.io/
|
384
|
+
_documentation_url = "https://docs.prefect.io/latest/automate/events/automations-triggers#sending-notifications-with-automations"
|
364
385
|
|
365
386
|
apikey: SecretStr = Field(
|
366
387
|
default=...,
|
@@ -478,7 +499,7 @@ class MattermostWebhook(AbstractAppriseNotificationBlock):
|
|
478
499
|
_block_type_name = "Mattermost Webhook"
|
479
500
|
_block_type_slug = "mattermost-webhook"
|
480
501
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/1350a147130bf82cbc799a5f868d2c0116207736-250x250.png"
|
481
|
-
_documentation_url = "https://docs.prefect.io/
|
502
|
+
_documentation_url = "https://docs.prefect.io/latest/automate/events/automations-triggers#sending-notifications-with-automations"
|
482
503
|
|
483
504
|
hostname: str = Field(
|
484
505
|
default=...,
|
@@ -559,7 +580,7 @@ class DiscordWebhook(AbstractAppriseNotificationBlock):
|
|
559
580
|
_block_type_name = "Discord Webhook"
|
560
581
|
_block_type_slug = "discord-webhook"
|
561
582
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/9e94976c80ef925b66d24e5d14f0d47baa6b8f88-250x250.png"
|
562
|
-
_documentation_url = "https://docs.prefect.io/
|
583
|
+
_documentation_url = "https://docs.prefect.io/latest/automate/events/automations-triggers#sending-notifications-with-automations"
|
563
584
|
|
564
585
|
webhook_id: SecretStr = Field(
|
565
586
|
default=...,
|
@@ -658,7 +679,7 @@ class CustomWebhookNotificationBlock(NotificationBlock):
|
|
658
679
|
|
659
680
|
_block_type_name = "Custom Webhook"
|
660
681
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/c7247cb359eb6cf276734d4b1fbf00fb8930e89e-250x250.png"
|
661
|
-
_documentation_url = "https://docs.prefect.io/
|
682
|
+
_documentation_url = "https://docs.prefect.io/latest/automate/events/automations-triggers#sending-notifications-with-automations"
|
662
683
|
|
663
684
|
name: str = Field(title="Name", description="Name of the webhook.")
|
664
685
|
|
@@ -789,7 +810,7 @@ class SendgridEmail(AbstractAppriseNotificationBlock):
|
|
789
810
|
_block_type_name = "Sendgrid Email"
|
790
811
|
_block_type_slug = "sendgrid-email"
|
791
812
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/82bc6ed16ca42a2252a5512c72233a253b8a58eb-250x250.png"
|
792
|
-
_documentation_url = "https://docs.prefect.io/
|
813
|
+
_documentation_url = "https://docs.prefect.io/latest/automate/events/automations-triggers#sending-notifications-with-automations"
|
793
814
|
|
794
815
|
api_key: SecretStr = Field(
|
795
816
|
default=...,
|
prefect/blocks/system.py
CHANGED
@@ -44,7 +44,7 @@ class JSON(Block):
|
|
44
44
|
"""
|
45
45
|
|
46
46
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/4fcef2294b6eeb423b1332d1ece5156bf296ff96-48x48.png"
|
47
|
-
_documentation_url = "https://docs.prefect.io/
|
47
|
+
_documentation_url = "https://docs.prefect.io/latest/develop/blocks"
|
48
48
|
|
49
49
|
value: Any = Field(default=..., description="A JSON-compatible value.")
|
50
50
|
|
@@ -71,7 +71,7 @@ class String(Block):
|
|
71
71
|
"""
|
72
72
|
|
73
73
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/c262ea2c80a2c043564e8763f3370c3db5a6b3e6-48x48.png"
|
74
|
-
_documentation_url = "https://docs.prefect.io/
|
74
|
+
_documentation_url = "https://docs.prefect.io/latest/develop/blocks"
|
75
75
|
|
76
76
|
value: str = Field(default=..., description="A string value.")
|
77
77
|
|
@@ -99,7 +99,7 @@ class DateTime(Block):
|
|
99
99
|
|
100
100
|
_block_type_name = "Date Time"
|
101
101
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/8b3da9a6621e92108b8e6a75b82e15374e170ff7-48x48.png"
|
102
|
-
_documentation_url = "https://docs.prefect.io/
|
102
|
+
_documentation_url = "https://docs.prefect.io/latest/develop/blocks"
|
103
103
|
|
104
104
|
value: PydanticDateTime = Field(
|
105
105
|
default=...,
|
@@ -129,7 +129,7 @@ class Secret(Block, Generic[T]):
|
|
129
129
|
"""
|
130
130
|
|
131
131
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/c6f20e556dd16effda9df16551feecfb5822092b-48x48.png"
|
132
|
-
_documentation_url = "https://docs.prefect.io/
|
132
|
+
_documentation_url = "https://docs.prefect.io/latest/develop/blocks"
|
133
133
|
|
134
134
|
value: Union[SecretStr, PydanticSecret[T]] = Field(
|
135
135
|
default=...,
|
prefect/blocks/webhook.py
CHANGED
@@ -6,6 +6,7 @@ from typing_extensions import Literal
|
|
6
6
|
|
7
7
|
from prefect.blocks.core import Block
|
8
8
|
from prefect.types import SecretDict
|
9
|
+
from prefect.utilities.urls import validate_restricted_url
|
9
10
|
|
10
11
|
# Use a global HTTP transport to maintain a process-wide connection pool for
|
11
12
|
# interservice requests
|
@@ -19,7 +20,9 @@ class Webhook(Block):
|
|
19
20
|
|
20
21
|
_block_type_name = "Webhook"
|
21
22
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/c7247cb359eb6cf276734d4b1fbf00fb8930e89e-250x250.png" # type: ignore
|
22
|
-
_documentation_url =
|
23
|
+
_documentation_url = (
|
24
|
+
"https://docs.prefect.io/latest/automate/events/webhook-triggers"
|
25
|
+
)
|
23
26
|
|
24
27
|
method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"] = Field(
|
25
28
|
default="POST", description="The webhook request method. Defaults to `POST`."
|
@@ -37,6 +40,10 @@ class Webhook(Block):
|
|
37
40
|
title="Webhook Headers",
|
38
41
|
description="A dictionary of headers to send with the webhook request.",
|
39
42
|
)
|
43
|
+
allow_private_urls: bool = Field(
|
44
|
+
default=True,
|
45
|
+
description="Whether to allow notifications to private URLs. Defaults to True.",
|
46
|
+
)
|
40
47
|
|
41
48
|
def block_initialization(self):
|
42
49
|
self._client = AsyncClient(transport=_http_transport)
|
@@ -48,6 +55,9 @@ class Webhook(Block):
|
|
48
55
|
Args:
|
49
56
|
payload: an optional payload to send when calling the webhook.
|
50
57
|
"""
|
58
|
+
if not self.allow_private_urls:
|
59
|
+
validate_restricted_url(self.url.get_secret_value())
|
60
|
+
|
51
61
|
async with self._client:
|
52
62
|
return await self._client.request(
|
53
63
|
method=self.method,
|
prefect/client/cloud.py
CHANGED
@@ -73,9 +73,10 @@ class CloudClient:
|
|
73
73
|
**httpx_settings, enable_csrf_support=False
|
74
74
|
)
|
75
75
|
|
76
|
+
api_url = prefect.settings.PREFECT_API_URL.value() or ""
|
76
77
|
if match := (
|
77
78
|
re.search(PARSE_API_URL_REGEX, host)
|
78
|
-
or re.search(PARSE_API_URL_REGEX,
|
79
|
+
or re.search(PARSE_API_URL_REGEX, api_url)
|
79
80
|
):
|
80
81
|
self.account_id, self.workspace_id = match.groups()
|
81
82
|
|
prefect/client/orchestration.py
CHANGED
@@ -24,6 +24,7 @@ import httpx
|
|
24
24
|
import pendulum
|
25
25
|
import pydantic
|
26
26
|
from asgi_lifespan import LifespanManager
|
27
|
+
from packaging import version
|
27
28
|
from starlette import status
|
28
29
|
from typing_extensions import ParamSpec
|
29
30
|
|
@@ -93,7 +94,6 @@ from prefect.client.schemas.objects import (
|
|
93
94
|
FlowRunPolicy,
|
94
95
|
Log,
|
95
96
|
Parameter,
|
96
|
-
QueueFilter,
|
97
97
|
TaskRunPolicy,
|
98
98
|
TaskRunResult,
|
99
99
|
Variable,
|
@@ -993,7 +993,6 @@ class PrefectClient:
|
|
993
993
|
async def create_work_queue(
|
994
994
|
self,
|
995
995
|
name: str,
|
996
|
-
tags: Optional[List[str]] = None,
|
997
996
|
description: Optional[str] = None,
|
998
997
|
is_paused: Optional[bool] = None,
|
999
998
|
concurrency_limit: Optional[int] = None,
|
@@ -1005,8 +1004,6 @@ class PrefectClient:
|
|
1005
1004
|
|
1006
1005
|
Args:
|
1007
1006
|
name: a unique name for the work queue
|
1008
|
-
tags: DEPRECATED: an optional list of tags to filter on; only work scheduled with these tags
|
1009
|
-
will be included in the queue. This option will be removed on 2023-02-23.
|
1010
1007
|
description: An optional description for the work queue.
|
1011
1008
|
is_paused: Whether or not the work queue is paused.
|
1012
1009
|
concurrency_limit: An optional concurrency limit for the work queue.
|
@@ -1020,18 +1017,7 @@ class PrefectClient:
|
|
1020
1017
|
Returns:
|
1021
1018
|
The created work queue
|
1022
1019
|
"""
|
1023
|
-
|
1024
|
-
warnings.warn(
|
1025
|
-
(
|
1026
|
-
"The use of tags for creating work queue filters is deprecated."
|
1027
|
-
" This option will be removed on 2023-02-23."
|
1028
|
-
),
|
1029
|
-
DeprecationWarning,
|
1030
|
-
)
|
1031
|
-
filter = QueueFilter(tags=tags)
|
1032
|
-
else:
|
1033
|
-
filter = None
|
1034
|
-
create_model = WorkQueueCreate(name=name, filter=filter)
|
1020
|
+
create_model = WorkQueueCreate(name=name, filter=None)
|
1035
1021
|
if description is not None:
|
1036
1022
|
create_model.description = description
|
1037
1023
|
if is_paused is not None:
|
@@ -2157,7 +2143,10 @@ class PrefectClient:
|
|
2157
2143
|
try:
|
2158
2144
|
response = await self._client.post(
|
2159
2145
|
f"/flow_runs/{flow_run_id}/set_state",
|
2160
|
-
json=dict(
|
2146
|
+
json=dict(
|
2147
|
+
state=state_create.model_dump(mode="json", serialize_as_any=True),
|
2148
|
+
force=force,
|
2149
|
+
),
|
2161
2150
|
)
|
2162
2151
|
except httpx.HTTPStatusError as e:
|
2163
2152
|
if e.response.status_code == status.HTTP_404_NOT_FOUND:
|
@@ -3054,7 +3043,11 @@ class PrefectClient:
|
|
3054
3043
|
return response.json()
|
3055
3044
|
|
3056
3045
|
async def increment_concurrency_slots(
|
3057
|
-
self,
|
3046
|
+
self,
|
3047
|
+
names: List[str],
|
3048
|
+
slots: int,
|
3049
|
+
mode: str,
|
3050
|
+
create_if_missing: Optional[bool] = None,
|
3058
3051
|
) -> httpx.Response:
|
3059
3052
|
return await self._client.post(
|
3060
3053
|
"/v2/concurrency_limits/increment",
|
@@ -3062,7 +3055,7 @@ class PrefectClient:
|
|
3062
3055
|
"names": names,
|
3063
3056
|
"slots": slots,
|
3064
3057
|
"mode": mode,
|
3065
|
-
"create_if_missing": create_if_missing,
|
3058
|
+
"create_if_missing": create_if_missing if create_if_missing else False,
|
3066
3059
|
},
|
3067
3060
|
)
|
3068
3061
|
|
@@ -3139,6 +3132,30 @@ class PrefectClient:
|
|
3139
3132
|
else:
|
3140
3133
|
raise
|
3141
3134
|
|
3135
|
+
async def upsert_global_concurrency_limit_by_name(self, name: str, limit: int):
|
3136
|
+
"""Creates a global concurrency limit with the given name and limit if one does not already exist.
|
3137
|
+
|
3138
|
+
If one does already exist matching the name then update it's limit if it is different.
|
3139
|
+
|
3140
|
+
Note: This is not done atomically.
|
3141
|
+
"""
|
3142
|
+
try:
|
3143
|
+
existing_limit = await self.read_global_concurrency_limit_by_name(name)
|
3144
|
+
except prefect.exceptions.ObjectNotFound:
|
3145
|
+
existing_limit = None
|
3146
|
+
|
3147
|
+
if not existing_limit:
|
3148
|
+
await self.create_global_concurrency_limit(
|
3149
|
+
GlobalConcurrencyLimitCreate(
|
3150
|
+
name=name,
|
3151
|
+
limit=limit,
|
3152
|
+
)
|
3153
|
+
)
|
3154
|
+
elif existing_limit.limit != limit:
|
3155
|
+
await self.update_global_concurrency_limit(
|
3156
|
+
name, GlobalConcurrencyLimitUpdate(limit=limit)
|
3157
|
+
)
|
3158
|
+
|
3142
3159
|
async def read_global_concurrency_limits(
|
3143
3160
|
self, limit: int = 10, offset: int = 0
|
3144
3161
|
) -> List[GlobalConcurrencyLimitResponse]:
|
@@ -3329,6 +3346,32 @@ class PrefectClient:
|
|
3329
3346
|
async def delete_resource_owned_automations(self, resource_id: str):
|
3330
3347
|
await self._client.delete(f"/automations/owned-by/{resource_id}")
|
3331
3348
|
|
3349
|
+
async def api_version(self) -> str:
|
3350
|
+
res = await self._client.get("/admin/version")
|
3351
|
+
return res.json()
|
3352
|
+
|
3353
|
+
def client_version(self) -> str:
|
3354
|
+
return prefect.__version__
|
3355
|
+
|
3356
|
+
async def raise_for_api_version_mismatch(self):
|
3357
|
+
# Cloud is always compatible as a server
|
3358
|
+
if self.server_type == ServerType.CLOUD:
|
3359
|
+
return
|
3360
|
+
|
3361
|
+
try:
|
3362
|
+
api_version = await self.api_version()
|
3363
|
+
except Exception as e:
|
3364
|
+
raise RuntimeError(f"Failed to reach API at {self.api_url}") from e
|
3365
|
+
|
3366
|
+
api_version = version.parse(api_version)
|
3367
|
+
client_version = version.parse(self.client_version())
|
3368
|
+
|
3369
|
+
if api_version.major != client_version.major:
|
3370
|
+
raise RuntimeError(
|
3371
|
+
f"Found incompatible versions: client: {client_version}, server: {api_version}. "
|
3372
|
+
f"Major versions must match."
|
3373
|
+
)
|
3374
|
+
|
3332
3375
|
async def __aenter__(self):
|
3333
3376
|
"""
|
3334
3377
|
Start the client.
|
@@ -3622,6 +3665,32 @@ class SyncPrefectClient:
|
|
3622
3665
|
"""
|
3623
3666
|
return self._client.get("/hello")
|
3624
3667
|
|
3668
|
+
def api_version(self) -> str:
|
3669
|
+
res = self._client.get("/admin/version")
|
3670
|
+
return res.json()
|
3671
|
+
|
3672
|
+
def client_version(self) -> str:
|
3673
|
+
return prefect.__version__
|
3674
|
+
|
3675
|
+
def raise_for_api_version_mismatch(self):
|
3676
|
+
# Cloud is always compatible as a server
|
3677
|
+
if self.server_type == ServerType.CLOUD:
|
3678
|
+
return
|
3679
|
+
|
3680
|
+
try:
|
3681
|
+
api_version = self.api_version()
|
3682
|
+
except Exception as e:
|
3683
|
+
raise RuntimeError(f"Failed to reach API at {self.api_url}") from e
|
3684
|
+
|
3685
|
+
api_version = version.parse(api_version)
|
3686
|
+
client_version = version.parse(self.client_version())
|
3687
|
+
|
3688
|
+
if api_version.major != client_version.major:
|
3689
|
+
raise RuntimeError(
|
3690
|
+
f"Found incompatible versions: client: {client_version}, server: {api_version}. "
|
3691
|
+
f"Major versions must match."
|
3692
|
+
)
|
3693
|
+
|
3625
3694
|
def create_flow(self, flow: "FlowObject") -> UUID:
|
3626
3695
|
"""
|
3627
3696
|
Create a flow in the Prefect API.
|
@@ -3881,7 +3950,10 @@ class SyncPrefectClient:
|
|
3881
3950
|
try:
|
3882
3951
|
response = self._client.post(
|
3883
3952
|
f"/flow_runs/{flow_run_id}/set_state",
|
3884
|
-
json=dict(
|
3953
|
+
json=dict(
|
3954
|
+
state=state_create.model_dump(mode="json", serialize_as_any=True),
|
3955
|
+
force=force,
|
3956
|
+
),
|
3885
3957
|
)
|
3886
3958
|
except httpx.HTTPStatusError as e:
|
3887
3959
|
if e.response.status_code == status.HTTP_404_NOT_FOUND:
|
@@ -4147,7 +4219,7 @@ class SyncPrefectClient:
|
|
4147
4219
|
|
4148
4220
|
response = self._client.post(
|
4149
4221
|
"/artifacts/",
|
4150
|
-
json=artifact.
|
4222
|
+
json=artifact.model_dump(mode="json", exclude_unset=True),
|
4151
4223
|
)
|
4152
4224
|
|
4153
4225
|
return Artifact.model_validate(response.json())
|
@@ -38,7 +38,7 @@ from prefect.utilities.collections import listrepr
|
|
38
38
|
from prefect.utilities.pydantic import get_class_fields_only
|
39
39
|
|
40
40
|
if TYPE_CHECKING:
|
41
|
-
from prefect.results import BaseResult
|
41
|
+
from prefect.results import BaseResult, ResultRecordMetadata
|
42
42
|
|
43
43
|
R = TypeVar("R")
|
44
44
|
|
@@ -50,7 +50,7 @@ class StateCreate(ActionBaseModel):
|
|
50
50
|
name: Optional[str] = Field(default=None)
|
51
51
|
message: Optional[str] = Field(default=None, examples=["Run started"])
|
52
52
|
state_details: StateDetails = Field(default_factory=StateDetails)
|
53
|
-
data: Union["BaseResult[R]", Any] = Field(
|
53
|
+
data: Union["BaseResult[R]", "ResultRecordMetadata", Any] = Field(
|
54
54
|
default=None,
|
55
55
|
)
|
56
56
|
|
@@ -3,6 +3,7 @@ import warnings
|
|
3
3
|
from functools import partial
|
4
4
|
from typing import (
|
5
5
|
TYPE_CHECKING,
|
6
|
+
Annotated,
|
6
7
|
Any,
|
7
8
|
Dict,
|
8
9
|
Generic,
|
@@ -17,10 +18,12 @@ import orjson
|
|
17
18
|
import pendulum
|
18
19
|
from pydantic import (
|
19
20
|
ConfigDict,
|
21
|
+
Discriminator,
|
20
22
|
Field,
|
21
23
|
HttpUrl,
|
22
24
|
IPvAnyNetwork,
|
23
25
|
SerializationInfo,
|
26
|
+
Tag,
|
24
27
|
field_validator,
|
25
28
|
model_serializer,
|
26
29
|
model_validator,
|
@@ -59,7 +62,7 @@ from prefect.utilities.names import generate_slug
|
|
59
62
|
from prefect.utilities.pydantic import handle_secret_render
|
60
63
|
|
61
64
|
if TYPE_CHECKING:
|
62
|
-
from prefect.results import BaseResult
|
65
|
+
from prefect.results import BaseResult, ResultRecordMetadata
|
63
66
|
|
64
67
|
|
65
68
|
R = TypeVar("R", default=Any)
|
@@ -158,6 +161,14 @@ class StateDetails(PrefectBaseModel):
|
|
158
161
|
task_parameters_id: Optional[UUID] = None
|
159
162
|
|
160
163
|
|
164
|
+
def data_discriminator(x: Any) -> str:
|
165
|
+
if isinstance(x, dict) and "type" in x:
|
166
|
+
return "BaseResult"
|
167
|
+
elif isinstance(x, dict) and "storage_key" in x:
|
168
|
+
return "ResultRecordMetadata"
|
169
|
+
return "Any"
|
170
|
+
|
171
|
+
|
161
172
|
class State(ObjectBaseModel, Generic[R]):
|
162
173
|
"""
|
163
174
|
The state of a run.
|
@@ -168,9 +179,14 @@ class State(ObjectBaseModel, Generic[R]):
|
|
168
179
|
timestamp: DateTime = Field(default_factory=lambda: pendulum.now("UTC"))
|
169
180
|
message: Optional[str] = Field(default=None, examples=["Run started"])
|
170
181
|
state_details: StateDetails = Field(default_factory=StateDetails)
|
171
|
-
data:
|
172
|
-
|
173
|
-
|
182
|
+
data: Annotated[
|
183
|
+
Union[
|
184
|
+
Annotated["BaseResult[R]", Tag("BaseResult")],
|
185
|
+
Annotated["ResultRecordMetadata", Tag("ResultRecordMetadata")],
|
186
|
+
Annotated[Any, Tag("Any")],
|
187
|
+
],
|
188
|
+
Discriminator(data_discriminator),
|
189
|
+
] = Field(default=None)
|
174
190
|
|
175
191
|
@overload
|
176
192
|
def result(self: "State[R]", raise_on_failure: bool = True) -> R:
|
@@ -276,10 +292,12 @@ class State(ObjectBaseModel, Generic[R]):
|
|
276
292
|
results should be sent to the API. Other data is only available locally.
|
277
293
|
"""
|
278
294
|
from prefect.client.schemas.actions import StateCreate
|
279
|
-
from prefect.results import BaseResult
|
295
|
+
from prefect.results import BaseResult, ResultRecord, should_persist_result
|
280
296
|
|
281
|
-
if isinstance(self.data, BaseResult)
|
297
|
+
if isinstance(self.data, BaseResult):
|
282
298
|
data = self.data
|
299
|
+
elif isinstance(self.data, ResultRecord) and should_persist_result():
|
300
|
+
data = self.data.metadata
|
283
301
|
else:
|
284
302
|
data = None
|
285
303
|
|
@@ -7,5 +7,5 @@ if TYPE_CHECKING:
|
|
7
7
|
from prefect.client.schemas.schedules import SCHEDULE_TYPES
|
8
8
|
|
9
9
|
FlexibleScheduleList: TypeAlias = Sequence[
|
10
|
-
Union[DeploymentScheduleCreate, dict[str, Any], "SCHEDULE_TYPES"]
|
10
|
+
Union["DeploymentScheduleCreate", dict[str, Any], "SCHEDULE_TYPES"]
|
11
11
|
]
|