prefect-client 3.1.4__py3-none-any.whl → 3.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prefect/__init__.py +3 -0
- prefect/_internal/compatibility/migration.py +1 -1
- prefect/_internal/concurrency/api.py +52 -52
- prefect/_internal/concurrency/calls.py +59 -35
- prefect/_internal/concurrency/cancellation.py +34 -18
- prefect/_internal/concurrency/event_loop.py +7 -6
- prefect/_internal/concurrency/threads.py +41 -33
- prefect/_internal/concurrency/waiters.py +28 -21
- prefect/_internal/pydantic/v1_schema.py +2 -2
- prefect/_internal/pydantic/v2_schema.py +10 -9
- prefect/_internal/schemas/bases.py +10 -11
- prefect/_internal/schemas/validators.py +2 -1
- prefect/_version.py +3 -3
- prefect/automations.py +53 -47
- prefect/blocks/abstract.py +12 -10
- prefect/blocks/core.py +4 -2
- prefect/cache_policies.py +11 -11
- prefect/client/__init__.py +3 -1
- prefect/client/base.py +36 -37
- prefect/client/cloud.py +26 -19
- prefect/client/collections.py +2 -2
- prefect/client/orchestration.py +366 -277
- prefect/client/schemas/__init__.py +24 -0
- prefect/client/schemas/actions.py +132 -120
- prefect/client/schemas/filters.py +5 -0
- prefect/client/schemas/objects.py +113 -85
- prefect/client/schemas/responses.py +21 -18
- prefect/client/schemas/schedules.py +136 -93
- prefect/client/subscriptions.py +28 -14
- prefect/client/utilities.py +32 -36
- prefect/concurrency/asyncio.py +6 -9
- prefect/concurrency/services.py +3 -0
- prefect/concurrency/sync.py +35 -5
- prefect/context.py +39 -31
- prefect/deployments/flow_runs.py +3 -5
- prefect/docker/__init__.py +1 -1
- prefect/events/schemas/events.py +25 -20
- prefect/events/utilities.py +1 -2
- prefect/filesystems.py +3 -3
- prefect/flow_engine.py +755 -138
- prefect/flow_runs.py +3 -3
- prefect/flows.py +214 -170
- prefect/logging/configuration.py +1 -1
- prefect/logging/highlighters.py +1 -2
- prefect/logging/loggers.py +30 -20
- prefect/main.py +17 -24
- prefect/runner/runner.py +43 -21
- prefect/runner/server.py +30 -32
- prefect/runner/submit.py +3 -6
- prefect/runner/utils.py +6 -6
- prefect/runtime/flow_run.py +7 -0
- prefect/settings/constants.py +2 -2
- prefect/settings/legacy.py +1 -1
- prefect/settings/models/server/events.py +10 -0
- prefect/settings/sources.py +9 -2
- prefect/task_engine.py +72 -19
- prefect/task_runners.py +2 -2
- prefect/tasks.py +46 -33
- prefect/telemetry/bootstrap.py +15 -2
- prefect/telemetry/run_telemetry.py +107 -0
- prefect/transactions.py +14 -14
- prefect/types/__init__.py +20 -3
- prefect/utilities/_engine.py +96 -0
- prefect/utilities/annotations.py +25 -18
- prefect/utilities/asyncutils.py +126 -140
- prefect/utilities/callables.py +87 -78
- prefect/utilities/collections.py +278 -117
- prefect/utilities/compat.py +13 -21
- prefect/utilities/context.py +6 -5
- prefect/utilities/dispatch.py +23 -12
- prefect/utilities/dockerutils.py +33 -32
- prefect/utilities/engine.py +126 -239
- prefect/utilities/filesystem.py +18 -15
- prefect/utilities/hashing.py +10 -11
- prefect/utilities/importtools.py +40 -27
- prefect/utilities/math.py +9 -5
- prefect/utilities/names.py +3 -3
- prefect/utilities/processutils.py +121 -57
- prefect/utilities/pydantic.py +41 -36
- prefect/utilities/render_swagger.py +22 -12
- prefect/utilities/schema_tools/__init__.py +2 -1
- prefect/utilities/schema_tools/hydration.py +50 -43
- prefect/utilities/schema_tools/validation.py +52 -42
- prefect/utilities/services.py +13 -12
- prefect/utilities/templating.py +45 -45
- prefect/utilities/text.py +2 -1
- prefect/utilities/timeout.py +4 -4
- prefect/utilities/urls.py +9 -4
- prefect/utilities/visualization.py +46 -24
- prefect/variables.py +9 -8
- prefect/workers/base.py +18 -10
- {prefect_client-3.1.4.dist-info → prefect_client-3.1.6.dist-info}/METADATA +5 -5
- {prefect_client-3.1.4.dist-info → prefect_client-3.1.6.dist-info}/RECORD +96 -94
- {prefect_client-3.1.4.dist-info → prefect_client-3.1.6.dist-info}/WHEEL +1 -1
- {prefect_client-3.1.4.dist-info → prefect_client-3.1.6.dist-info}/LICENSE +0 -0
- {prefect_client-3.1.4.dist-info → prefect_client-3.1.6.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@ Utilities for creating and working with Prefect REST API schemas.
|
|
4
4
|
|
5
5
|
import datetime
|
6
6
|
import os
|
7
|
-
from typing import Any, ClassVar, Optional, Set, TypeVar
|
7
|
+
from typing import Any, ClassVar, Generator, Optional, Set, TypeVar, cast
|
8
8
|
from uuid import UUID, uuid4
|
9
9
|
|
10
10
|
import pendulum
|
@@ -33,7 +33,7 @@ class PrefectBaseModel(BaseModel):
|
|
33
33
|
|
34
34
|
_reset_fields: ClassVar[Set[str]] = set()
|
35
35
|
|
36
|
-
model_config = ConfigDict(
|
36
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(
|
37
37
|
ser_json_timedelta="float",
|
38
38
|
defer_build=True,
|
39
39
|
extra=(
|
@@ -58,7 +58,7 @@ class PrefectBaseModel(BaseModel):
|
|
58
58
|
else:
|
59
59
|
return copy_dict == other
|
60
60
|
|
61
|
-
def __rich_repr__(self):
|
61
|
+
def __rich_repr__(self) -> Generator[tuple[str, Any, Any], None, None]:
|
62
62
|
# Display all of the fields in the model if they differ from the default value
|
63
63
|
for name, field in self.model_fields.items():
|
64
64
|
value = getattr(self, name)
|
@@ -71,9 +71,11 @@ class PrefectBaseModel(BaseModel):
|
|
71
71
|
and name == "timestamp"
|
72
72
|
and value
|
73
73
|
):
|
74
|
-
value = pendulum.instance(value).isoformat()
|
74
|
+
value = cast(pendulum.DateTime, pendulum.instance(value)).isoformat()
|
75
75
|
elif isinstance(field.annotation, datetime.datetime) and value:
|
76
|
-
value =
|
76
|
+
value = cast(
|
77
|
+
pendulum.DateTime, pendulum.instance(value)
|
78
|
+
).diff_for_humans()
|
77
79
|
|
78
80
|
yield name, value, field.get_default()
|
79
81
|
|
@@ -84,12 +86,9 @@ class PrefectBaseModel(BaseModel):
|
|
84
86
|
Returns:
|
85
87
|
PrefectBaseModel: A new instance of the model with the reset fields.
|
86
88
|
"""
|
87
|
-
data = self.model_dump()
|
88
89
|
return self.model_copy(
|
89
90
|
update={
|
90
|
-
field: self.model_fields[field].get_default(
|
91
|
-
call_default_factory=True, validated_data=data
|
92
|
-
)
|
91
|
+
field: self.model_fields[field].get_default(call_default_factory=True)
|
93
92
|
for field in self._reset_fields
|
94
93
|
}
|
95
94
|
)
|
@@ -116,11 +115,11 @@ class ObjectBaseModel(IDBaseModel):
|
|
116
115
|
"""
|
117
116
|
|
118
117
|
_reset_fields: ClassVar[Set[str]] = {"id", "created", "updated"}
|
119
|
-
model_config = ConfigDict(from_attributes=True)
|
118
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(from_attributes=True)
|
120
119
|
|
121
120
|
created: Optional[DateTime] = Field(default=None, repr=False)
|
122
121
|
updated: Optional[DateTime] = Field(default=None, repr=False)
|
123
122
|
|
124
123
|
|
125
124
|
class ActionBaseModel(PrefectBaseModel):
|
126
|
-
model_config: ConfigDict = ConfigDict(extra="forbid")
|
125
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
@@ -13,6 +13,7 @@ import warnings
|
|
13
13
|
from copy import copy
|
14
14
|
from pathlib import Path
|
15
15
|
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple, Union
|
16
|
+
from uuid import UUID
|
16
17
|
|
17
18
|
import jsonschema
|
18
19
|
import pendulum
|
@@ -653,7 +654,7 @@ def validate_message_template_variables(v: Optional[str]) -> Optional[str]:
|
|
653
654
|
return v
|
654
655
|
|
655
656
|
|
656
|
-
def validate_default_queue_id_not_none(v: Optional[
|
657
|
+
def validate_default_queue_id_not_none(v: Optional[UUID]) -> UUID:
|
657
658
|
if v is None:
|
658
659
|
raise ValueError(
|
659
660
|
"`default_queue_id` is a required field. If you are "
|
prefect/_version.py
CHANGED
@@ -8,11 +8,11 @@ import json
|
|
8
8
|
|
9
9
|
version_json = '''
|
10
10
|
{
|
11
|
-
"date": "2024-
|
11
|
+
"date": "2024-12-11T10:46:23-0500",
|
12
12
|
"dirty": true,
|
13
13
|
"error": null,
|
14
|
-
"full-revisionid": "
|
15
|
-
"version": "3.1.
|
14
|
+
"full-revisionid": "6bfce9e87c6e7e3e44c65d8b5ecc6ab156f2c7c8",
|
15
|
+
"version": "3.1.6"
|
16
16
|
}
|
17
17
|
''' # END VERSION_JSON
|
18
18
|
|
prefect/automations.py
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
from typing import Optional
|
1
|
+
from typing import Optional, Type
|
2
2
|
from uuid import UUID
|
3
3
|
|
4
4
|
from pydantic import Field
|
5
5
|
from typing_extensions import Self
|
6
6
|
|
7
|
-
from prefect.client.
|
7
|
+
from prefect.client.orchestration import get_client
|
8
8
|
from prefect.events.actions import (
|
9
9
|
CallWebhook,
|
10
10
|
CancelFlowRun,
|
@@ -99,10 +99,10 @@ class Automation(AutomationCore):
|
|
99
99
|
)
|
100
100
|
created_automation = auto_to_create.create()
|
101
101
|
"""
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
102
|
+
async with get_client() as client:
|
103
|
+
automation = AutomationCore(**self.model_dump(exclude={"id"}))
|
104
|
+
self.id = await client.create_automation(automation=automation)
|
105
|
+
return self
|
106
106
|
|
107
107
|
@sync_compatible
|
108
108
|
async def update(self: Self):
|
@@ -112,15 +112,16 @@ class Automation(AutomationCore):
|
|
112
112
|
auto.name = "new name"
|
113
113
|
auto.update()
|
114
114
|
"""
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
115
|
+
async with get_client() as client:
|
116
|
+
automation = AutomationCore(
|
117
|
+
**self.model_dump(exclude={"id", "owner_resource"})
|
118
|
+
)
|
119
|
+
await client.update_automation(automation_id=self.id, automation=automation)
|
119
120
|
|
120
121
|
@classmethod
|
121
122
|
@sync_compatible
|
122
123
|
async def read(
|
123
|
-
cls: Self, id: Optional[UUID] = None, name: Optional[str] = None
|
124
|
+
cls: Type[Self], id: Optional[UUID] = None, name: Optional[str] = None
|
124
125
|
) -> Self:
|
125
126
|
"""
|
126
127
|
Read an automation by ID or name.
|
@@ -134,20 +135,25 @@ class Automation(AutomationCore):
|
|
134
135
|
raise ValueError("Only one of id or name can be provided")
|
135
136
|
if not id and not name:
|
136
137
|
raise ValueError("One of id or name must be provided")
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
138
|
+
async with get_client() as client:
|
139
|
+
if id:
|
140
|
+
try:
|
141
|
+
automation = await client.read_automation(automation_id=id)
|
142
|
+
except PrefectHTTPStatusError as exc:
|
143
|
+
if exc.response.status_code == 404:
|
144
|
+
raise ValueError(f"Automation with ID {id!r} not found")
|
145
|
+
raise
|
146
|
+
if automation is None:
|
143
147
|
raise ValueError(f"Automation with ID {id!r} not found")
|
144
|
-
|
145
|
-
else:
|
146
|
-
automation = await client.read_automations_by_name(name=name)
|
147
|
-
if len(automation) > 0:
|
148
|
-
return Automation(**automation[0].model_dump()) if automation else None
|
148
|
+
return Automation(**automation.model_dump())
|
149
149
|
else:
|
150
|
-
|
150
|
+
automation = await client.read_automations_by_name(name=name)
|
151
|
+
if len(automation) > 0:
|
152
|
+
return (
|
153
|
+
Automation(**automation[0].model_dump()) if automation else None
|
154
|
+
)
|
155
|
+
else:
|
156
|
+
raise ValueError(f"Automation with name {name!r} not found")
|
151
157
|
|
152
158
|
@sync_compatible
|
153
159
|
async def delete(self: Self) -> bool:
|
@@ -155,14 +161,14 @@ class Automation(AutomationCore):
|
|
155
161
|
auto = Automation.read(id = 123)
|
156
162
|
auto.delete()
|
157
163
|
"""
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
164
|
+
async with get_client() as client:
|
165
|
+
try:
|
166
|
+
await client.delete_automation(self.id)
|
167
|
+
return True
|
168
|
+
except PrefectHTTPStatusError as exc:
|
169
|
+
if exc.response.status_code == 404:
|
170
|
+
return False
|
171
|
+
raise
|
166
172
|
|
167
173
|
@sync_compatible
|
168
174
|
async def disable(self: Self) -> bool:
|
@@ -171,14 +177,14 @@ class Automation(AutomationCore):
|
|
171
177
|
auto = Automation.read(id = 123)
|
172
178
|
auto.disable()
|
173
179
|
"""
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
180
|
+
async with get_client() as client:
|
181
|
+
try:
|
182
|
+
await client.pause_automation(self.id)
|
183
|
+
return True
|
184
|
+
except PrefectHTTPStatusError as exc:
|
185
|
+
if exc.response.status_code == 404:
|
186
|
+
return False
|
187
|
+
raise
|
182
188
|
|
183
189
|
@sync_compatible
|
184
190
|
async def enable(self: Self) -> bool:
|
@@ -187,11 +193,11 @@ class Automation(AutomationCore):
|
|
187
193
|
auto = Automation.read(id = 123)
|
188
194
|
auto.enable()
|
189
195
|
"""
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
196
|
+
async with get_client() as client:
|
197
|
+
try:
|
198
|
+
await client.resume_automation(self.id)
|
199
|
+
return True
|
200
|
+
except PrefectHTTPStatusError as exc:
|
201
|
+
if exc.response.status_code == 404:
|
202
|
+
return False
|
203
|
+
raise
|
prefect/blocks/abstract.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
2
|
from contextlib import contextmanager
|
3
|
-
from logging import Logger
|
3
|
+
from logging import Logger, LoggerAdapter
|
4
4
|
from pathlib import Path
|
5
5
|
from typing import (
|
6
6
|
Any,
|
@@ -15,7 +15,7 @@ from typing import (
|
|
15
15
|
Union,
|
16
16
|
)
|
17
17
|
|
18
|
-
from typing_extensions import Self
|
18
|
+
from typing_extensions import Self, TypeAlias
|
19
19
|
|
20
20
|
from prefect.blocks.core import Block
|
21
21
|
from prefect.exceptions import MissingContextError
|
@@ -23,6 +23,8 @@ from prefect.logging.loggers import get_logger, get_run_logger
|
|
23
23
|
|
24
24
|
T = TypeVar("T")
|
25
25
|
|
26
|
+
LoggerOrAdapter: TypeAlias = Union[Logger, LoggerAdapter]
|
27
|
+
|
26
28
|
|
27
29
|
class CredentialsBlock(Block, ABC):
|
28
30
|
"""
|
@@ -34,7 +36,7 @@ class CredentialsBlock(Block, ABC):
|
|
34
36
|
"""
|
35
37
|
|
36
38
|
@property
|
37
|
-
def logger(self) ->
|
39
|
+
def logger(self) -> LoggerOrAdapter:
|
38
40
|
"""
|
39
41
|
Returns a logger based on whether the CredentialsBlock
|
40
42
|
is called from within a flow or task run context.
|
@@ -73,10 +75,10 @@ class NotificationBlock(Block, ABC):
|
|
73
75
|
"""
|
74
76
|
|
75
77
|
_block_schema_capabilities = ["notify"]
|
76
|
-
_events_excluded_methods = Block._events_excluded_methods
|
78
|
+
_events_excluded_methods = Block._events_excluded_methods + ["notify"]
|
77
79
|
|
78
80
|
@property
|
79
|
-
def logger(self) ->
|
81
|
+
def logger(self) -> LoggerOrAdapter:
|
80
82
|
"""
|
81
83
|
Returns a logger based on whether the NotificationBlock
|
82
84
|
is called from within a flow or task run context.
|
@@ -123,7 +125,7 @@ class JobRun(ABC, Generic[T]): # not a block
|
|
123
125
|
"""
|
124
126
|
|
125
127
|
@property
|
126
|
-
def logger(self) ->
|
128
|
+
def logger(self) -> LoggerOrAdapter:
|
127
129
|
"""
|
128
130
|
Returns a logger based on whether the JobRun
|
129
131
|
is called from within a flow or task run context.
|
@@ -158,7 +160,7 @@ class JobBlock(Block, ABC):
|
|
158
160
|
"""
|
159
161
|
|
160
162
|
@property
|
161
|
-
def logger(self) ->
|
163
|
+
def logger(self) -> LoggerOrAdapter:
|
162
164
|
"""
|
163
165
|
Returns a logger based on whether the JobBlock
|
164
166
|
is called from within a flow or task run context.
|
@@ -202,7 +204,7 @@ class DatabaseBlock(Block, ABC):
|
|
202
204
|
"""
|
203
205
|
|
204
206
|
@property
|
205
|
-
def logger(self) ->
|
207
|
+
def logger(self) -> LoggerOrAdapter:
|
206
208
|
"""
|
207
209
|
Returns a logger based on whether the DatabaseBlock
|
208
210
|
is called from within a flow or task run context.
|
@@ -337,7 +339,7 @@ class ObjectStorageBlock(Block, ABC):
|
|
337
339
|
"""
|
338
340
|
|
339
341
|
@property
|
340
|
-
def logger(self) ->
|
342
|
+
def logger(self) -> LoggerOrAdapter:
|
341
343
|
"""
|
342
344
|
Returns a logger based on whether the ObjectStorageBlock
|
343
345
|
is called from within a flow or task run context.
|
@@ -469,7 +471,7 @@ class SecretBlock(Block, ABC):
|
|
469
471
|
"""
|
470
472
|
|
471
473
|
@property
|
472
|
-
def logger(self) ->
|
474
|
+
def logger(self) -> LoggerOrAdapter:
|
473
475
|
"""
|
474
476
|
Returns a logger based on whether the SecretBlock
|
475
477
|
is called from within a flow or task run context.
|
prefect/blocks/core.py
CHANGED
@@ -326,7 +326,9 @@ class Block(BaseModel, ABC):
|
|
326
326
|
|
327
327
|
# Exclude `save` as it uses the `sync_compatible` decorator and needs to be
|
328
328
|
# decorated directly.
|
329
|
-
_events_excluded_methods
|
329
|
+
_events_excluded_methods: ClassVar[List[str]] = PrivateAttr(
|
330
|
+
default=["block_initialization", "save", "dict"]
|
331
|
+
)
|
330
332
|
|
331
333
|
@classmethod
|
332
334
|
def __dispatch_key__(cls):
|
@@ -1229,7 +1231,7 @@ class Block(BaseModel, ABC):
|
|
1229
1231
|
"""
|
1230
1232
|
block_type_slug = kwargs.pop("block_type_slug", None)
|
1231
1233
|
if block_type_slug:
|
1232
|
-
subcls =
|
1234
|
+
subcls = cls.get_block_class_from_key(block_type_slug)
|
1233
1235
|
return super().__new__(subcls)
|
1234
1236
|
else:
|
1235
1237
|
return super().__new__(cls)
|
prefect/cache_policies.py
CHANGED
@@ -75,12 +75,12 @@ class CachePolicy:
|
|
75
75
|
task_ctx: TaskRunContext,
|
76
76
|
inputs: Dict[str, Any],
|
77
77
|
flow_parameters: Dict[str, Any],
|
78
|
-
**kwargs,
|
78
|
+
**kwargs: Any,
|
79
79
|
) -> Optional[str]:
|
80
80
|
raise NotImplementedError
|
81
81
|
|
82
82
|
def __sub__(self, other: str) -> "CachePolicy":
|
83
|
-
if not isinstance(other, str):
|
83
|
+
if not isinstance(other, str): # type: ignore[reportUnnecessaryIsInstance]
|
84
84
|
raise TypeError("Can only subtract strings from key policies.")
|
85
85
|
new = Inputs(exclude=[other])
|
86
86
|
return CompoundCachePolicy(policies=[self, new])
|
@@ -140,7 +140,7 @@ class CacheKeyFnPolicy(CachePolicy):
|
|
140
140
|
task_ctx: TaskRunContext,
|
141
141
|
inputs: Dict[str, Any],
|
142
142
|
flow_parameters: Dict[str, Any],
|
143
|
-
**kwargs,
|
143
|
+
**kwargs: Any,
|
144
144
|
) -> Optional[str]:
|
145
145
|
if self.cache_key_fn:
|
146
146
|
return self.cache_key_fn(task_ctx, inputs)
|
@@ -162,9 +162,9 @@ class CompoundCachePolicy(CachePolicy):
|
|
162
162
|
task_ctx: TaskRunContext,
|
163
163
|
inputs: Dict[str, Any],
|
164
164
|
flow_parameters: Dict[str, Any],
|
165
|
-
**kwargs,
|
165
|
+
**kwargs: Any,
|
166
166
|
) -> Optional[str]:
|
167
|
-
keys = []
|
167
|
+
keys: list[str] = []
|
168
168
|
for policy in self.policies:
|
169
169
|
policy_key = policy.compute_key(
|
170
170
|
task_ctx=task_ctx,
|
@@ -191,7 +191,7 @@ class _None(CachePolicy):
|
|
191
191
|
task_ctx: TaskRunContext,
|
192
192
|
inputs: Dict[str, Any],
|
193
193
|
flow_parameters: Dict[str, Any],
|
194
|
-
**kwargs,
|
194
|
+
**kwargs: Any,
|
195
195
|
) -> Optional[str]:
|
196
196
|
return None
|
197
197
|
|
@@ -211,7 +211,7 @@ class TaskSource(CachePolicy):
|
|
211
211
|
task_ctx: TaskRunContext,
|
212
212
|
inputs: Optional[Dict[str, Any]],
|
213
213
|
flow_parameters: Optional[Dict[str, Any]],
|
214
|
-
**kwargs,
|
214
|
+
**kwargs: Any,
|
215
215
|
) -> Optional[str]:
|
216
216
|
if not task_ctx:
|
217
217
|
return None
|
@@ -238,7 +238,7 @@ class FlowParameters(CachePolicy):
|
|
238
238
|
task_ctx: TaskRunContext,
|
239
239
|
inputs: Dict[str, Any],
|
240
240
|
flow_parameters: Dict[str, Any],
|
241
|
-
**kwargs,
|
241
|
+
**kwargs: Any,
|
242
242
|
) -> Optional[str]:
|
243
243
|
if not flow_parameters:
|
244
244
|
return None
|
@@ -257,7 +257,7 @@ class RunId(CachePolicy):
|
|
257
257
|
task_ctx: TaskRunContext,
|
258
258
|
inputs: Dict[str, Any],
|
259
259
|
flow_parameters: Dict[str, Any],
|
260
|
-
**kwargs,
|
260
|
+
**kwargs: Any,
|
261
261
|
) -> Optional[str]:
|
262
262
|
if not task_ctx:
|
263
263
|
return None
|
@@ -280,7 +280,7 @@ class Inputs(CachePolicy):
|
|
280
280
|
task_ctx: TaskRunContext,
|
281
281
|
inputs: Dict[str, Any],
|
282
282
|
flow_parameters: Dict[str, Any],
|
283
|
-
**kwargs,
|
283
|
+
**kwargs: Any,
|
284
284
|
) -> Optional[str]:
|
285
285
|
hashed_inputs = {}
|
286
286
|
inputs = inputs or {}
|
@@ -307,7 +307,7 @@ class Inputs(CachePolicy):
|
|
307
307
|
raise ValueError(msg) from exc
|
308
308
|
|
309
309
|
def __sub__(self, other: str) -> "CachePolicy":
|
310
|
-
if not isinstance(other, str):
|
310
|
+
if not isinstance(other, str): # type: ignore[reportUnnecessaryIsInstance]
|
311
311
|
raise TypeError("Can only subtract strings from key policies.")
|
312
312
|
return Inputs(exclude=self.exclude + [other])
|
313
313
|
|
prefect/client/__init__.py
CHANGED
@@ -16,6 +16,8 @@ $ python -m asyncio
|
|
16
16
|
</div>
|
17
17
|
"""
|
18
18
|
|
19
|
+
from collections.abc import Callable
|
20
|
+
from typing import Any
|
19
21
|
from prefect._internal.compatibility.migration import getattr_migration
|
20
22
|
|
21
|
-
__getattr__ = getattr_migration(__name__)
|
23
|
+
__getattr__: Callable[[str], Any] = getattr_migration(__name__)
|
prefect/client/base.py
CHANGED
@@ -4,22 +4,11 @@ import threading
|
|
4
4
|
import time
|
5
5
|
import uuid
|
6
6
|
from collections import defaultdict
|
7
|
+
from collections.abc import AsyncGenerator, Awaitable, MutableMapping
|
7
8
|
from contextlib import asynccontextmanager
|
8
9
|
from datetime import datetime, timezone
|
9
|
-
from
|
10
|
-
|
11
|
-
AsyncGenerator,
|
12
|
-
Awaitable,
|
13
|
-
Callable,
|
14
|
-
Dict,
|
15
|
-
MutableMapping,
|
16
|
-
Optional,
|
17
|
-
Protocol,
|
18
|
-
Set,
|
19
|
-
Tuple,
|
20
|
-
Type,
|
21
|
-
runtime_checkable,
|
22
|
-
)
|
10
|
+
from logging import Logger
|
11
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Protocol, runtime_checkable
|
23
12
|
|
24
13
|
import anyio
|
25
14
|
import httpx
|
@@ -46,14 +35,14 @@ from prefect.utilities.math import bounded_poisson_interval, clamped_poisson_int
|
|
46
35
|
|
47
36
|
# Datastores for lifespan management, keys should be a tuple of thread and app
|
48
37
|
# identities.
|
49
|
-
APP_LIFESPANS:
|
50
|
-
APP_LIFESPANS_REF_COUNTS:
|
38
|
+
APP_LIFESPANS: dict[tuple[int, int], LifespanManager] = {}
|
39
|
+
APP_LIFESPANS_REF_COUNTS: dict[tuple[int, int], int] = {}
|
51
40
|
# Blocks concurrent access to the above dicts per thread. The index should be the thread
|
52
41
|
# identity.
|
53
|
-
APP_LIFESPANS_LOCKS:
|
42
|
+
APP_LIFESPANS_LOCKS: dict[int, anyio.Lock] = defaultdict(anyio.Lock)
|
54
43
|
|
55
44
|
|
56
|
-
logger = get_logger("client")
|
45
|
+
logger: Logger = get_logger("client")
|
57
46
|
|
58
47
|
|
59
48
|
# Define ASGI application types for type checking
|
@@ -161,7 +150,7 @@ class PrefectResponse(httpx.Response):
|
|
161
150
|
Provides more informative error messages.
|
162
151
|
"""
|
163
152
|
|
164
|
-
def raise_for_status(self) ->
|
153
|
+
def raise_for_status(self) -> Response:
|
165
154
|
"""
|
166
155
|
Raise an exception if the response contains an HTTPStatusError.
|
167
156
|
|
@@ -174,9 +163,9 @@ class PrefectResponse(httpx.Response):
|
|
174
163
|
raise PrefectHTTPStatusError.from_httpx_error(exc) from exc.__cause__
|
175
164
|
|
176
165
|
@classmethod
|
177
|
-
def from_httpx_response(cls:
|
166
|
+
def from_httpx_response(cls: type[Self], response: httpx.Response) -> Response:
|
178
167
|
"""
|
179
|
-
Create a `
|
168
|
+
Create a `PrefectResponse` from an `httpx.Response`.
|
180
169
|
|
181
170
|
By changing the `__class__` attribute of the Response, we change the method
|
182
171
|
resolution order to look for methods defined in PrefectResponse, while leaving
|
@@ -200,10 +189,10 @@ class PrefectHttpxAsyncClient(httpx.AsyncClient):
|
|
200
189
|
|
201
190
|
def __init__(
|
202
191
|
self,
|
203
|
-
*args,
|
192
|
+
*args: Any,
|
204
193
|
enable_csrf_support: bool = False,
|
205
194
|
raise_on_all_errors: bool = True,
|
206
|
-
**kwargs,
|
195
|
+
**kwargs: Any,
|
207
196
|
):
|
208
197
|
self.enable_csrf_support: bool = enable_csrf_support
|
209
198
|
self.csrf_token: Optional[str] = None
|
@@ -222,10 +211,10 @@ class PrefectHttpxAsyncClient(httpx.AsyncClient):
|
|
222
211
|
self,
|
223
212
|
request: Request,
|
224
213
|
send: Callable[[Request], Awaitable[Response]],
|
225
|
-
send_args:
|
226
|
-
send_kwargs:
|
227
|
-
retry_codes:
|
228
|
-
retry_exceptions:
|
214
|
+
send_args: tuple[Any, ...],
|
215
|
+
send_kwargs: dict[str, Any],
|
216
|
+
retry_codes: set[int] = set(),
|
217
|
+
retry_exceptions: tuple[type[Exception], ...] = tuple(),
|
229
218
|
):
|
230
219
|
"""
|
231
220
|
Send a request and retry it if it fails.
|
@@ -240,6 +229,11 @@ class PrefectHttpxAsyncClient(httpx.AsyncClient):
|
|
240
229
|
try_count = 0
|
241
230
|
response = None
|
242
231
|
|
232
|
+
if TYPE_CHECKING:
|
233
|
+
# older httpx versions type method as str | bytes | Unknown
|
234
|
+
# but in reality it is always a string.
|
235
|
+
assert isinstance(request.method, str) # type: ignore
|
236
|
+
|
243
237
|
is_change_request = request.method.lower() in {"post", "put", "patch", "delete"}
|
244
238
|
|
245
239
|
if self.enable_csrf_support and is_change_request:
|
@@ -297,7 +291,7 @@ class PrefectHttpxAsyncClient(httpx.AsyncClient):
|
|
297
291
|
if exc_info
|
298
292
|
else (
|
299
293
|
"Received response with retryable status code"
|
300
|
-
f" {response.status_code}. "
|
294
|
+
f" {response.status_code if response else 'unknown'}. "
|
301
295
|
)
|
302
296
|
)
|
303
297
|
+ f"Another attempt will be made in {retry_seconds}s. "
|
@@ -314,7 +308,7 @@ class PrefectHttpxAsyncClient(httpx.AsyncClient):
|
|
314
308
|
# We ran out of retries, return the failed response
|
315
309
|
return response
|
316
310
|
|
317
|
-
async def send(self, request: Request, *args, **kwargs) -> Response:
|
311
|
+
async def send(self, request: Request, *args: Any, **kwargs: Any) -> Response:
|
318
312
|
"""
|
319
313
|
Send a request with automatic retry behavior for the following status codes:
|
320
314
|
|
@@ -414,10 +408,10 @@ class PrefectHttpxSyncClient(httpx.Client):
|
|
414
408
|
|
415
409
|
def __init__(
|
416
410
|
self,
|
417
|
-
*args,
|
411
|
+
*args: Any,
|
418
412
|
enable_csrf_support: bool = False,
|
419
413
|
raise_on_all_errors: bool = True,
|
420
|
-
**kwargs,
|
414
|
+
**kwargs: Any,
|
421
415
|
):
|
422
416
|
self.enable_csrf_support: bool = enable_csrf_support
|
423
417
|
self.csrf_token: Optional[str] = None
|
@@ -436,10 +430,10 @@ class PrefectHttpxSyncClient(httpx.Client):
|
|
436
430
|
self,
|
437
431
|
request: Request,
|
438
432
|
send: Callable[[Request], Response],
|
439
|
-
send_args:
|
440
|
-
send_kwargs:
|
441
|
-
retry_codes:
|
442
|
-
retry_exceptions:
|
433
|
+
send_args: tuple[Any, ...],
|
434
|
+
send_kwargs: dict[str, Any],
|
435
|
+
retry_codes: set[int] = set(),
|
436
|
+
retry_exceptions: tuple[type[Exception], ...] = tuple(),
|
443
437
|
):
|
444
438
|
"""
|
445
439
|
Send a request and retry it if it fails.
|
@@ -454,6 +448,11 @@ class PrefectHttpxSyncClient(httpx.Client):
|
|
454
448
|
try_count = 0
|
455
449
|
response = None
|
456
450
|
|
451
|
+
if TYPE_CHECKING:
|
452
|
+
# older httpx versions type method as str | bytes | Unknown
|
453
|
+
# but in reality it is always a string.
|
454
|
+
assert isinstance(request.method, str) # type: ignore
|
455
|
+
|
457
456
|
is_change_request = request.method.lower() in {"post", "put", "patch", "delete"}
|
458
457
|
|
459
458
|
if self.enable_csrf_support and is_change_request:
|
@@ -511,7 +510,7 @@ class PrefectHttpxSyncClient(httpx.Client):
|
|
511
510
|
if exc_info
|
512
511
|
else (
|
513
512
|
"Received response with retryable status code"
|
514
|
-
f" {response.status_code}. "
|
513
|
+
f" {response.status_code if response else 'unknown'}. "
|
515
514
|
)
|
516
515
|
)
|
517
516
|
+ f"Another attempt will be made in {retry_seconds}s. "
|
@@ -528,7 +527,7 @@ class PrefectHttpxSyncClient(httpx.Client):
|
|
528
527
|
# We ran out of retries, return the failed response
|
529
528
|
return response
|
530
529
|
|
531
|
-
def send(self, request: Request, *args, **kwargs) -> Response:
|
530
|
+
def send(self, request: Request, *args: Any, **kwargs: Any) -> Response:
|
532
531
|
"""
|
533
532
|
Send a request with automatic retry behavior for the following status codes:
|
534
533
|
|