prefect-client 3.0.0rc19__py3-none-any.whl → 3.0.0rc20__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 +0 -3
- prefect/blocks/core.py +5 -1
- prefect/blocks/system.py +48 -12
- prefect/client/cloud.py +56 -7
- prefect/client/collections.py +1 -1
- prefect/client/orchestration.py +14 -6
- prefect/client/schemas/objects.py +40 -2
- prefect/concurrency/asyncio.py +8 -2
- prefect/concurrency/services.py +16 -6
- prefect/concurrency/sync.py +4 -1
- prefect/exceptions.py +6 -0
- prefect/flow_engine.py +3 -0
- prefect/flows.py +2 -2
- prefect/logging/handlers.py +4 -1
- prefect/main.py +8 -6
- prefect/results.py +222 -170
- prefect/settings.py +14 -7
- prefect/states.py +73 -18
- prefect/task_engine.py +29 -12
- prefect/transactions.py +22 -8
- prefect/utilities/annotations.py +4 -3
- prefect/utilities/asyncutils.py +1 -1
- prefect/utilities/dispatch.py +16 -11
- prefect/utilities/schema_tools/hydration.py +13 -0
- prefect/workers/base.py +78 -18
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.0rc20.dist-info}/METADATA +1 -1
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.0rc20.dist-info}/RECORD +30 -31
- prefect/manifests.py +0 -21
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.0rc20.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.0rc20.dist-info}/WHEEL +0 -0
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.0rc20.dist-info}/top_level.txt +0 -0
prefect/__init__.py
CHANGED
@@ -31,7 +31,6 @@ if TYPE_CHECKING:
|
|
31
31
|
Flow,
|
32
32
|
get_client,
|
33
33
|
get_run_logger,
|
34
|
-
Manifest,
|
35
34
|
State,
|
36
35
|
tags,
|
37
36
|
task,
|
@@ -60,7 +59,6 @@ _public_api: dict[str, tuple[str, str]] = {
|
|
60
59
|
"Flow": (__spec__.parent, ".main"),
|
61
60
|
"get_client": (__spec__.parent, ".main"),
|
62
61
|
"get_run_logger": (__spec__.parent, ".main"),
|
63
|
-
"Manifest": (__spec__.parent, ".main"),
|
64
62
|
"State": (__spec__.parent, ".main"),
|
65
63
|
"tags": (__spec__.parent, ".main"),
|
66
64
|
"task": (__spec__.parent, ".main"),
|
@@ -81,7 +79,6 @@ __all__ = [
|
|
81
79
|
"Flow",
|
82
80
|
"get_client",
|
83
81
|
"get_run_logger",
|
84
|
-
"Manifest",
|
85
82
|
"State",
|
86
83
|
"tags",
|
87
84
|
"task",
|
prefect/blocks/core.py
CHANGED
@@ -150,7 +150,11 @@ def _collect_secret_fields(
|
|
150
150
|
_collect_secret_fields(f"{name}.{field_name}", field.annotation, secrets)
|
151
151
|
return
|
152
152
|
|
153
|
-
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
|
+
):
|
154
158
|
secrets.append(name)
|
155
159
|
elif type_ == SecretDict:
|
156
160
|
# Append .* to field name to signify that all values under this
|
prefect/blocks/system.py
CHANGED
@@ -1,11 +1,26 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
from
|
1
|
+
import json
|
2
|
+
from typing import Annotated, Any, Generic, TypeVar, Union
|
3
|
+
|
4
|
+
from pydantic import (
|
5
|
+
Field,
|
6
|
+
JsonValue,
|
7
|
+
SecretStr,
|
8
|
+
StrictStr,
|
9
|
+
field_validator,
|
10
|
+
)
|
11
|
+
from pydantic import Secret as PydanticSecret
|
12
|
+
from pydantic_extra_types.pendulum_dt import DateTime as PydanticDateTime
|
5
13
|
|
6
14
|
from prefect._internal.compatibility.deprecated import deprecated_class
|
7
15
|
from prefect.blocks.core import Block
|
8
16
|
|
17
|
+
_SecretValueType = Union[
|
18
|
+
Annotated[StrictStr, Field(title="string")],
|
19
|
+
Annotated[JsonValue, Field(title="JSON")],
|
20
|
+
]
|
21
|
+
|
22
|
+
T = TypeVar("T", bound=_SecretValueType)
|
23
|
+
|
9
24
|
|
10
25
|
@deprecated_class(
|
11
26
|
start_date="Jun 2024",
|
@@ -86,24 +101,26 @@ class DateTime(Block):
|
|
86
101
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/8b3da9a6621e92108b8e6a75b82e15374e170ff7-48x48.png"
|
87
102
|
_documentation_url = "https://docs.prefect.io/api-ref/prefect/blocks/system/#prefect.blocks.system.DateTime"
|
88
103
|
|
89
|
-
value:
|
104
|
+
value: PydanticDateTime = Field(
|
90
105
|
default=...,
|
91
106
|
description="An ISO 8601-compatible datetime value.",
|
92
107
|
)
|
93
108
|
|
94
109
|
|
95
|
-
class Secret(Block):
|
110
|
+
class Secret(Block, Generic[T]):
|
96
111
|
"""
|
97
112
|
A block that represents a secret value. The value stored in this block will be obfuscated when
|
98
|
-
this block is
|
113
|
+
this block is viewed or edited in the UI.
|
99
114
|
|
100
115
|
Attributes:
|
101
|
-
value: A
|
116
|
+
value: A value that should be kept secret.
|
102
117
|
|
103
118
|
Example:
|
104
119
|
```python
|
105
120
|
from prefect.blocks.system import Secret
|
106
121
|
|
122
|
+
Secret(value="sk-1234567890").save("BLOCK_NAME", overwrite=True)
|
123
|
+
|
107
124
|
secret_block = Secret.load("BLOCK_NAME")
|
108
125
|
|
109
126
|
# Access the stored secret
|
@@ -114,9 +131,28 @@ class Secret(Block):
|
|
114
131
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/c6f20e556dd16effda9df16551feecfb5822092b-48x48.png"
|
115
132
|
_documentation_url = "https://docs.prefect.io/api-ref/prefect/blocks/system/#prefect.blocks.system.Secret"
|
116
133
|
|
117
|
-
value: SecretStr = Field(
|
118
|
-
default=...,
|
134
|
+
value: Union[SecretStr, PydanticSecret[T]] = Field(
|
135
|
+
default=...,
|
136
|
+
description="A value that should be kept secret.",
|
137
|
+
examples=["sk-1234567890", {"username": "johndoe", "password": "s3cr3t"}],
|
138
|
+
json_schema_extra={
|
139
|
+
"writeOnly": True,
|
140
|
+
"format": "password",
|
141
|
+
},
|
119
142
|
)
|
120
143
|
|
121
|
-
|
122
|
-
|
144
|
+
@field_validator("value", mode="before")
|
145
|
+
def validate_value(
|
146
|
+
cls, value: Union[T, SecretStr, PydanticSecret[T]]
|
147
|
+
) -> Union[SecretStr, PydanticSecret[T]]:
|
148
|
+
if isinstance(value, (PydanticSecret, SecretStr)):
|
149
|
+
return value
|
150
|
+
else:
|
151
|
+
return PydanticSecret[type(value)](value)
|
152
|
+
|
153
|
+
def get(self) -> T:
|
154
|
+
try:
|
155
|
+
value = self.value.get_secret_value()
|
156
|
+
return json.loads(value)
|
157
|
+
except (TypeError, json.JSONDecodeError):
|
158
|
+
return value
|
prefect/client/cloud.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import re
|
2
|
-
from typing import Any, Dict, List, Optional
|
2
|
+
from typing import Any, Dict, List, Optional, cast
|
3
3
|
|
4
4
|
import anyio
|
5
5
|
import httpx
|
@@ -9,7 +9,11 @@ from starlette import status
|
|
9
9
|
import prefect.context
|
10
10
|
import prefect.settings
|
11
11
|
from prefect.client.base import PrefectHttpxAsyncClient
|
12
|
-
from prefect.client.schemas.objects import
|
12
|
+
from prefect.client.schemas.objects import (
|
13
|
+
IPAllowlist,
|
14
|
+
IPAllowlistMyAccessResponse,
|
15
|
+
Workspace,
|
16
|
+
)
|
13
17
|
from prefect.exceptions import ObjectNotFound, PrefectException
|
14
18
|
from prefect.settings import (
|
15
19
|
PREFECT_API_KEY,
|
@@ -69,6 +73,26 @@ class CloudClient:
|
|
69
73
|
**httpx_settings, enable_csrf_support=False
|
70
74
|
)
|
71
75
|
|
76
|
+
if match := (
|
77
|
+
re.search(PARSE_API_URL_REGEX, host)
|
78
|
+
or re.search(PARSE_API_URL_REGEX, prefect.settings.PREFECT_API_URL.value())
|
79
|
+
):
|
80
|
+
self.account_id, self.workspace_id = match.groups()
|
81
|
+
|
82
|
+
@property
|
83
|
+
def account_base_url(self) -> str:
|
84
|
+
if not self.account_id:
|
85
|
+
raise ValueError("Account ID not set")
|
86
|
+
|
87
|
+
return f"accounts/{self.account_id}"
|
88
|
+
|
89
|
+
@property
|
90
|
+
def workspace_base_url(self) -> str:
|
91
|
+
if not self.workspace_id:
|
92
|
+
raise ValueError("Workspace ID not set")
|
93
|
+
|
94
|
+
return f"{self.account_base_url}/workspaces/{self.workspace_id}"
|
95
|
+
|
72
96
|
async def api_healthcheck(self):
|
73
97
|
"""
|
74
98
|
Attempts to connect to the Cloud API and raises the encountered exception if not
|
@@ -86,11 +110,36 @@ class CloudClient:
|
|
86
110
|
return workspaces
|
87
111
|
|
88
112
|
async def read_worker_metadata(self) -> Dict[str, Any]:
|
89
|
-
|
90
|
-
|
91
|
-
return await self.get(
|
92
|
-
f"accounts/{account_id}/workspaces/{workspace_id}/collections/work_pool_types"
|
113
|
+
response = await self.get(
|
114
|
+
f"{self.workspace_base_url}/collections/work_pool_types"
|
93
115
|
)
|
116
|
+
return cast(Dict[str, Any], response)
|
117
|
+
|
118
|
+
async def read_account_settings(self) -> Dict[str, Any]:
|
119
|
+
response = await self.get(f"{self.account_base_url}/settings")
|
120
|
+
return cast(Dict[str, Any], response)
|
121
|
+
|
122
|
+
async def update_account_settings(self, settings: Dict[str, Any]):
|
123
|
+
await self.request(
|
124
|
+
"PATCH",
|
125
|
+
f"{self.account_base_url}/settings",
|
126
|
+
json=settings,
|
127
|
+
)
|
128
|
+
|
129
|
+
async def read_account_ip_allowlist(self) -> IPAllowlist:
|
130
|
+
response = await self.get(f"{self.account_base_url}/ip_allowlist")
|
131
|
+
return IPAllowlist.model_validate(response)
|
132
|
+
|
133
|
+
async def update_account_ip_allowlist(self, updated_allowlist: IPAllowlist):
|
134
|
+
await self.request(
|
135
|
+
"PUT",
|
136
|
+
f"{self.account_base_url}/ip_allowlist",
|
137
|
+
json=updated_allowlist.model_dump(mode="json"),
|
138
|
+
)
|
139
|
+
|
140
|
+
async def check_ip_allowlist_access(self) -> IPAllowlistMyAccessResponse:
|
141
|
+
response = await self.get(f"{self.account_base_url}/ip_allowlist/my_access")
|
142
|
+
return IPAllowlistMyAccessResponse.model_validate(response)
|
94
143
|
|
95
144
|
async def __aenter__(self):
|
96
145
|
await self._client.__aenter__()
|
@@ -120,7 +169,7 @@ class CloudClient:
|
|
120
169
|
status.HTTP_401_UNAUTHORIZED,
|
121
170
|
status.HTTP_403_FORBIDDEN,
|
122
171
|
):
|
123
|
-
raise CloudUnauthorizedError
|
172
|
+
raise CloudUnauthorizedError(str(exc)) from exc
|
124
173
|
elif exc.response.status_code == status.HTTP_404_NOT_FOUND:
|
125
174
|
raise ObjectNotFound(http_exc=exc) from exc
|
126
175
|
else:
|
prefect/client/collections.py
CHANGED
@@ -29,6 +29,6 @@ def get_collections_metadata_client(
|
|
29
29
|
"""
|
30
30
|
orchestration_client = get_client(httpx_settings=httpx_settings)
|
31
31
|
if orchestration_client.server_type == ServerType.CLOUD:
|
32
|
-
return get_cloud_client(httpx_settings=httpx_settings)
|
32
|
+
return get_cloud_client(httpx_settings=httpx_settings, infer_cloud_url=True)
|
33
33
|
else:
|
34
34
|
return orchestration_client
|
prefect/client/orchestration.py
CHANGED
@@ -1324,15 +1324,17 @@ class PrefectClient:
|
|
1324
1324
|
`SecretBytes` fields. Note Blocks may not work as expected if
|
1325
1325
|
this is set to `False`.
|
1326
1326
|
"""
|
1327
|
+
block_document_data = block_document.model_dump(
|
1328
|
+
mode="json",
|
1329
|
+
exclude_unset=True,
|
1330
|
+
exclude={"id", "block_schema", "block_type"},
|
1331
|
+
context={"include_secrets": include_secrets},
|
1332
|
+
serialize_as_any=True,
|
1333
|
+
)
|
1327
1334
|
try:
|
1328
1335
|
response = await self._client.post(
|
1329
1336
|
"/block_documents/",
|
1330
|
-
json=
|
1331
|
-
mode="json",
|
1332
|
-
exclude_unset=True,
|
1333
|
-
exclude={"id", "block_schema", "block_type"},
|
1334
|
-
context={"include_secrets": include_secrets},
|
1335
|
-
),
|
1337
|
+
json=block_document_data,
|
1336
1338
|
)
|
1337
1339
|
except httpx.HTTPStatusError as e:
|
1338
1340
|
if e.response.status_code == status.HTTP_409_CONFLICT:
|
@@ -1786,6 +1788,12 @@ class PrefectClient:
|
|
1786
1788
|
Returns:
|
1787
1789
|
a [Deployment model][prefect.client.schemas.objects.Deployment] representation of the deployment
|
1788
1790
|
"""
|
1791
|
+
if not isinstance(deployment_id, UUID):
|
1792
|
+
try:
|
1793
|
+
deployment_id = UUID(deployment_id)
|
1794
|
+
except ValueError:
|
1795
|
+
raise ValueError(f"Invalid deployment ID: {deployment_id}")
|
1796
|
+
|
1789
1797
|
try:
|
1790
1798
|
response = await self._client.get(f"/deployments/{deployment_id}")
|
1791
1799
|
except httpx.HTTPStatusError as e:
|
@@ -19,11 +19,13 @@ from pydantic import (
|
|
19
19
|
ConfigDict,
|
20
20
|
Field,
|
21
21
|
HttpUrl,
|
22
|
+
IPvAnyNetwork,
|
22
23
|
SerializationInfo,
|
23
24
|
field_validator,
|
24
25
|
model_serializer,
|
25
26
|
model_validator,
|
26
27
|
)
|
28
|
+
from pydantic.functional_validators import ModelWrapValidatorHandler
|
27
29
|
from pydantic_extra_types.pendulum_dt import DateTime
|
28
30
|
from typing_extensions import Literal, Self, TypeVar
|
29
31
|
|
@@ -276,11 +278,16 @@ class State(ObjectBaseModel, Generic[R]):
|
|
276
278
|
from prefect.client.schemas.actions import StateCreate
|
277
279
|
from prefect.results import BaseResult
|
278
280
|
|
281
|
+
if isinstance(self.data, BaseResult) and self.data.serialize_to_none is False:
|
282
|
+
data = self.data
|
283
|
+
else:
|
284
|
+
data = None
|
285
|
+
|
279
286
|
return StateCreate(
|
280
287
|
type=self.type,
|
281
288
|
name=self.name,
|
282
289
|
message=self.message,
|
283
|
-
data=
|
290
|
+
data=data,
|
284
291
|
state_details=self.state_details,
|
285
292
|
)
|
286
293
|
|
@@ -848,6 +855,35 @@ class Workspace(PrefectBaseModel):
|
|
848
855
|
return hash(self.handle)
|
849
856
|
|
850
857
|
|
858
|
+
class IPAllowlistEntry(PrefectBaseModel):
|
859
|
+
ip_network: IPvAnyNetwork
|
860
|
+
enabled: bool
|
861
|
+
description: Optional[str] = Field(
|
862
|
+
default=None, description="A description of the IP entry."
|
863
|
+
)
|
864
|
+
last_seen: Optional[str] = Field(
|
865
|
+
default=None,
|
866
|
+
description="The last time this IP was seen accessing Prefect Cloud.",
|
867
|
+
)
|
868
|
+
|
869
|
+
|
870
|
+
class IPAllowlist(PrefectBaseModel):
|
871
|
+
"""
|
872
|
+
A Prefect Cloud IP allowlist.
|
873
|
+
|
874
|
+
Expected payload for an IP allowlist from the Prefect Cloud API.
|
875
|
+
"""
|
876
|
+
|
877
|
+
entries: List[IPAllowlistEntry]
|
878
|
+
|
879
|
+
|
880
|
+
class IPAllowlistMyAccessResponse(PrefectBaseModel):
|
881
|
+
"""Expected payload for an IP allowlist access response from the Prefect Cloud API."""
|
882
|
+
|
883
|
+
allowed: bool
|
884
|
+
detail: str
|
885
|
+
|
886
|
+
|
851
887
|
class BlockType(ObjectBaseModel):
|
852
888
|
"""An ORM representation of a block type"""
|
853
889
|
|
@@ -933,7 +969,9 @@ class BlockDocument(ObjectBaseModel):
|
|
933
969
|
return validate_name_present_on_nonanonymous_blocks(values)
|
934
970
|
|
935
971
|
@model_serializer(mode="wrap")
|
936
|
-
def serialize_data(
|
972
|
+
def serialize_data(
|
973
|
+
self, handler: ModelWrapValidatorHandler, info: SerializationInfo
|
974
|
+
):
|
937
975
|
self.data = visit_collection(
|
938
976
|
self.data,
|
939
977
|
visit_fn=partial(handle_secret_render, context=info.context or {}),
|
prefect/concurrency/asyncio.py
CHANGED
@@ -36,7 +36,8 @@ async def concurrency(
|
|
36
36
|
names: Union[str, List[str]],
|
37
37
|
occupy: int = 1,
|
38
38
|
timeout_seconds: Optional[float] = None,
|
39
|
-
create_if_missing:
|
39
|
+
create_if_missing: bool = True,
|
40
|
+
max_retries: Optional[int] = None,
|
40
41
|
) -> AsyncGenerator[None, None]:
|
41
42
|
"""A context manager that acquires and releases concurrency slots from the
|
42
43
|
given concurrency limits.
|
@@ -47,6 +48,7 @@ async def concurrency(
|
|
47
48
|
timeout_seconds: The number of seconds to wait for the slots to be acquired before
|
48
49
|
raising a `TimeoutError`. A timeout of `None` will wait indefinitely.
|
49
50
|
create_if_missing: Whether to create the concurrency limits if they do not exist.
|
51
|
+
max_retries: The maximum number of retries to acquire the concurrency slots.
|
50
52
|
|
51
53
|
Raises:
|
52
54
|
TimeoutError: If the slots are not acquired within the given timeout.
|
@@ -75,6 +77,7 @@ async def concurrency(
|
|
75
77
|
occupy,
|
76
78
|
timeout_seconds=timeout_seconds,
|
77
79
|
create_if_missing=create_if_missing,
|
80
|
+
max_retries=max_retries,
|
78
81
|
)
|
79
82
|
acquisition_time = pendulum.now("UTC")
|
80
83
|
emitted_events = _emit_concurrency_acquisition_events(limits, occupy)
|
@@ -137,9 +140,12 @@ async def _acquire_concurrency_slots(
|
|
137
140
|
mode: Union[Literal["concurrency"], Literal["rate_limit"]] = "concurrency",
|
138
141
|
timeout_seconds: Optional[float] = None,
|
139
142
|
create_if_missing: Optional[bool] = True,
|
143
|
+
max_retries: Optional[int] = None,
|
140
144
|
) -> List[MinimalConcurrencyLimitResponse]:
|
141
145
|
service = ConcurrencySlotAcquisitionService.instance(frozenset(names))
|
142
|
-
future = service.send(
|
146
|
+
future = service.send(
|
147
|
+
(slots, mode, timeout_seconds, create_if_missing, max_retries)
|
148
|
+
)
|
143
149
|
response_or_exception = await asyncio.wrap_future(future)
|
144
150
|
|
145
151
|
if isinstance(response_or_exception, Exception):
|
prefect/concurrency/services.py
CHANGED
@@ -36,13 +36,18 @@ class ConcurrencySlotAcquisitionService(QueueService):
|
|
36
36
|
async def _handle(
|
37
37
|
self,
|
38
38
|
item: Tuple[
|
39
|
-
int,
|
39
|
+
int,
|
40
|
+
str,
|
41
|
+
Optional[float],
|
42
|
+
concurrent.futures.Future,
|
43
|
+
Optional[bool],
|
44
|
+
Optional[int],
|
40
45
|
],
|
41
46
|
) -> None:
|
42
|
-
occupy, mode, timeout_seconds, future, create_if_missing = item
|
47
|
+
occupy, mode, timeout_seconds, future, create_if_missing, max_retries = item
|
43
48
|
try:
|
44
49
|
response = await self.acquire_slots(
|
45
|
-
occupy, mode, timeout_seconds, create_if_missing
|
50
|
+
occupy, mode, timeout_seconds, create_if_missing, max_retries
|
46
51
|
)
|
47
52
|
except Exception as exc:
|
48
53
|
# If the request to the increment endpoint fails in a non-standard
|
@@ -59,6 +64,7 @@ class ConcurrencySlotAcquisitionService(QueueService):
|
|
59
64
|
mode: str,
|
60
65
|
timeout_seconds: Optional[float] = None,
|
61
66
|
create_if_missing: Optional[bool] = False,
|
67
|
+
max_retries: Optional[int] = None,
|
62
68
|
) -> httpx.Response:
|
63
69
|
with timeout_async(seconds=timeout_seconds):
|
64
70
|
while True:
|
@@ -74,15 +80,19 @@ class ConcurrencySlotAcquisitionService(QueueService):
|
|
74
80
|
isinstance(exc, httpx.HTTPStatusError)
|
75
81
|
and exc.response.status_code == status.HTTP_423_LOCKED
|
76
82
|
):
|
83
|
+
if max_retries is not None and max_retries <= 0:
|
84
|
+
raise exc
|
77
85
|
retry_after = float(exc.response.headers["Retry-After"])
|
78
86
|
await asyncio.sleep(retry_after)
|
87
|
+
if max_retries is not None:
|
88
|
+
max_retries -= 1
|
79
89
|
else:
|
80
90
|
raise exc
|
81
91
|
else:
|
82
92
|
return response
|
83
93
|
|
84
94
|
def send(
|
85
|
-
self, item: Tuple[int, str, Optional[float], Optional[bool]]
|
95
|
+
self, item: Tuple[int, str, Optional[float], Optional[bool], Optional[int]]
|
86
96
|
) -> concurrent.futures.Future:
|
87
97
|
with self._lock:
|
88
98
|
if self._stopped:
|
@@ -91,9 +101,9 @@ class ConcurrencySlotAcquisitionService(QueueService):
|
|
91
101
|
logger.debug("Service %r enqueuing item %r", self, item)
|
92
102
|
future: concurrent.futures.Future = concurrent.futures.Future()
|
93
103
|
|
94
|
-
occupy, mode, timeout_seconds, create_if_missing = item
|
104
|
+
occupy, mode, timeout_seconds, create_if_missing, max_retries = item
|
95
105
|
self._queue.put_nowait(
|
96
|
-
(occupy, mode, timeout_seconds, future, create_if_missing)
|
106
|
+
(occupy, mode, timeout_seconds, future, create_if_missing, max_retries)
|
97
107
|
)
|
98
108
|
|
99
109
|
return future
|
prefect/concurrency/sync.py
CHANGED
@@ -40,7 +40,8 @@ def concurrency(
|
|
40
40
|
names: Union[str, List[str]],
|
41
41
|
occupy: int = 1,
|
42
42
|
timeout_seconds: Optional[float] = None,
|
43
|
-
create_if_missing:
|
43
|
+
create_if_missing: bool = True,
|
44
|
+
max_retries: Optional[int] = None,
|
44
45
|
) -> Generator[None, None, None]:
|
45
46
|
"""A context manager that acquires and releases concurrency slots from the
|
46
47
|
given concurrency limits.
|
@@ -51,6 +52,7 @@ def concurrency(
|
|
51
52
|
timeout_seconds: The number of seconds to wait for the slots to be acquired before
|
52
53
|
raising a `TimeoutError`. A timeout of `None` will wait indefinitely.
|
53
54
|
create_if_missing: Whether to create the concurrency limits if they do not exist.
|
55
|
+
max_retries: The maximum number of retries to acquire the concurrency slots.
|
54
56
|
|
55
57
|
Raises:
|
56
58
|
TimeoutError: If the slots are not acquired within the given timeout.
|
@@ -80,6 +82,7 @@ def concurrency(
|
|
80
82
|
occupy,
|
81
83
|
timeout_seconds=timeout_seconds,
|
82
84
|
create_if_missing=create_if_missing,
|
85
|
+
max_retries=max_retries,
|
83
86
|
)
|
84
87
|
acquisition_time = pendulum.now("UTC")
|
85
88
|
emitted_events = _emit_concurrency_acquisition_events(limits, occupy)
|
prefect/exceptions.py
CHANGED
prefect/flow_engine.py
CHANGED
@@ -205,6 +205,7 @@ class FlowRunEngine(Generic[P, R]):
|
|
205
205
|
result_factory=run_coro_as_sync(ResultFactory.from_flow(self.flow)),
|
206
206
|
)
|
207
207
|
self.short_circuit = True
|
208
|
+
self.call_hooks()
|
208
209
|
|
209
210
|
new_state = Running()
|
210
211
|
state = self.set_state(new_state)
|
@@ -268,6 +269,7 @@ class FlowRunEngine(Generic[P, R]):
|
|
268
269
|
return_value_to_state(
|
269
270
|
resolved_result,
|
270
271
|
result_factory=result_factory,
|
272
|
+
write_result=True,
|
271
273
|
)
|
272
274
|
)
|
273
275
|
self.set_state(terminal_state)
|
@@ -287,6 +289,7 @@ class FlowRunEngine(Generic[P, R]):
|
|
287
289
|
message=msg or "Flow run encountered an exception:",
|
288
290
|
result_factory=result_factory
|
289
291
|
or getattr(context, "result_factory", None),
|
292
|
+
write_result=True,
|
290
293
|
)
|
291
294
|
)
|
292
295
|
state = self.set_state(terminal_state)
|
prefect/flows.py
CHANGED
@@ -642,7 +642,7 @@ class Flow(Generic[P, R]):
|
|
642
642
|
cron: Optional[Union[Iterable[str], str]] = None,
|
643
643
|
rrule: Optional[Union[Iterable[str], str]] = None,
|
644
644
|
paused: Optional[bool] = None,
|
645
|
-
schedules: Optional[
|
645
|
+
schedules: Optional["FlexibleScheduleList"] = None,
|
646
646
|
concurrency_limit: Optional[int] = None,
|
647
647
|
parameters: Optional[dict] = None,
|
648
648
|
triggers: Optional[List[Union[DeploymentTriggerTypes, TriggerTypes]]] = None,
|
@@ -730,7 +730,7 @@ class Flow(Generic[P, R]):
|
|
730
730
|
work_pool_name=work_pool_name,
|
731
731
|
work_queue_name=work_queue_name,
|
732
732
|
job_variables=job_variables,
|
733
|
-
)
|
733
|
+
) # type: ignore # TODO: remove sync_compatible
|
734
734
|
else:
|
735
735
|
return RunnerDeployment.from_flow(
|
736
736
|
self,
|
prefect/logging/handlers.py
CHANGED
@@ -157,7 +157,10 @@ class APILogHandler(logging.Handler):
|
|
157
157
|
if log_handling_when_missing_flow == "warn":
|
158
158
|
# Warn when a logger is used outside of a run context, the stack level here
|
159
159
|
# gets us to the user logging call
|
160
|
-
warnings.warn(
|
160
|
+
warnings.warn(
|
161
|
+
f"{exc} Set PREFECT_LOGGING_TO_API_WHEN_MISSING_FLOW=ignore to suppress this warning.",
|
162
|
+
stacklevel=8,
|
163
|
+
)
|
161
164
|
return
|
162
165
|
elif log_handling_when_missing_flow == "ignore":
|
163
166
|
return
|
prefect/main.py
CHANGED
@@ -6,7 +6,6 @@ from prefect.flows import flow, Flow, serve
|
|
6
6
|
from prefect.transactions import Transaction
|
7
7
|
from prefect.tasks import task, Task
|
8
8
|
from prefect.context import tags
|
9
|
-
from prefect.manifests import Manifest
|
10
9
|
from prefect.utilities.annotations import unmapped, allow_failure
|
11
10
|
from prefect.results import BaseResult
|
12
11
|
from prefect.flow_runs import pause_flow_run, resume_flow_run, suspend_flow_run
|
@@ -26,10 +25,14 @@ import prefect.context
|
|
26
25
|
# Perform any forward-ref updates needed for Pydantic models
|
27
26
|
import prefect.client.schemas
|
28
27
|
|
29
|
-
prefect.context.FlowRunContext.model_rebuild(
|
30
|
-
|
31
|
-
|
32
|
-
prefect.
|
28
|
+
prefect.context.FlowRunContext.model_rebuild(
|
29
|
+
_types_namespace={"Flow": Flow, "BaseResult": BaseResult}
|
30
|
+
)
|
31
|
+
prefect.context.TaskRunContext.model_rebuild(_types_namespace={"Task": Task})
|
32
|
+
prefect.client.schemas.State.model_rebuild(_types_namespace={"BaseResult": BaseResult})
|
33
|
+
prefect.client.schemas.StateCreate.model_rebuild(
|
34
|
+
_types_namespace={"BaseResult": BaseResult}
|
35
|
+
)
|
33
36
|
Transaction.model_rebuild()
|
34
37
|
|
35
38
|
# Configure logging
|
@@ -55,7 +58,6 @@ __all__ = [
|
|
55
58
|
"Flow",
|
56
59
|
"get_client",
|
57
60
|
"get_run_logger",
|
58
|
-
"Manifest",
|
59
61
|
"State",
|
60
62
|
"tags",
|
61
63
|
"task",
|