prefect-client 2.18.2__py3-none-any.whl → 2.19.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 +1 -15
- prefect/_internal/concurrency/cancellation.py +2 -0
- prefect/_internal/schemas/validators.py +10 -0
- prefect/_vendor/starlette/testclient.py +1 -1
- prefect/blocks/notifications.py +6 -6
- prefect/client/base.py +244 -1
- prefect/client/cloud.py +4 -2
- prefect/client/orchestration.py +515 -106
- prefect/client/schemas/actions.py +58 -8
- prefect/client/schemas/objects.py +15 -1
- prefect/client/schemas/responses.py +19 -0
- prefect/client/schemas/schedules.py +1 -1
- prefect/client/utilities.py +2 -2
- prefect/concurrency/asyncio.py +34 -4
- prefect/concurrency/sync.py +40 -6
- prefect/context.py +2 -2
- prefect/engine.py +17 -1
- prefect/events/clients.py +2 -2
- prefect/flows.py +91 -17
- prefect/infrastructure/process.py +0 -17
- prefect/logging/formatters.py +1 -4
- prefect/new_flow_engine.py +166 -161
- prefect/new_task_engine.py +137 -202
- prefect/runner/__init__.py +1 -1
- prefect/runner/runner.py +2 -107
- prefect/settings.py +11 -0
- prefect/tasks.py +76 -57
- prefect/types/__init__.py +27 -5
- prefect/utilities/annotations.py +1 -8
- prefect/utilities/asyncutils.py +4 -0
- prefect/utilities/engine.py +106 -1
- prefect/utilities/schema_tools/__init__.py +6 -1
- prefect/utilities/schema_tools/validation.py +25 -8
- prefect/utilities/timeout.py +34 -0
- prefect/workers/base.py +7 -3
- prefect/workers/process.py +0 -17
- {prefect_client-2.18.2.dist-info → prefect_client-2.19.0.dist-info}/METADATA +1 -1
- {prefect_client-2.18.2.dist-info → prefect_client-2.19.0.dist-info}/RECORD +41 -40
- {prefect_client-2.18.2.dist-info → prefect_client-2.19.0.dist-info}/LICENSE +0 -0
- {prefect_client-2.18.2.dist-info → prefect_client-2.19.0.dist-info}/WHEEL +0 -0
- {prefect_client-2.18.2.dist-info → prefect_client-2.19.0.dist-info}/top_level.txt +0 -0
@@ -19,14 +19,17 @@ from prefect._internal.schemas.serializers import orjson_dumps_extra_compatible
|
|
19
19
|
from prefect._internal.schemas.validators import (
|
20
20
|
raise_on_name_alphanumeric_dashes_only,
|
21
21
|
raise_on_name_alphanumeric_underscores_only,
|
22
|
+
raise_on_name_with_banned_characters,
|
22
23
|
remove_old_deployment_fields,
|
23
24
|
return_none_schedule,
|
24
25
|
validate_message_template_variables,
|
25
26
|
validate_name_present_on_nonanonymous_blocks,
|
27
|
+
validate_schedule_max_scheduled_runs,
|
26
28
|
)
|
27
29
|
from prefect.client.schemas.objects import StateDetails, StateType
|
28
30
|
from prefect.client.schemas.schedules import SCHEDULE_TYPES
|
29
|
-
from prefect.
|
31
|
+
from prefect.settings import PREFECT_DEPLOYMENT_SCHEDULE_MAX_SCHEDULED_RUNS
|
32
|
+
from prefect.types import NonNegativeFloat, NonNegativeInteger, PositiveInteger
|
30
33
|
from prefect.utilities.collections import listrepr
|
31
34
|
from prefect.utilities.pydantic import get_class_fields_only
|
32
35
|
|
@@ -101,6 +104,24 @@ class DeploymentScheduleCreate(ActionBaseModel):
|
|
101
104
|
active: bool = Field(
|
102
105
|
default=True, description="Whether or not the schedule is active."
|
103
106
|
)
|
107
|
+
max_active_runs: Optional[PositiveInteger] = Field(
|
108
|
+
default=None,
|
109
|
+
description="The maximum number of active runs for the schedule.",
|
110
|
+
)
|
111
|
+
max_scheduled_runs: Optional[PositiveInteger] = Field(
|
112
|
+
default=None,
|
113
|
+
description="The maximum number of scheduled runs for the schedule.",
|
114
|
+
)
|
115
|
+
catchup: bool = Field(
|
116
|
+
default=False,
|
117
|
+
description="Whether or not a worker should catch up on Late runs for the schedule.",
|
118
|
+
)
|
119
|
+
|
120
|
+
@validator("max_scheduled_runs")
|
121
|
+
def validate_max_scheduled_runs(cls, v):
|
122
|
+
return validate_schedule_max_scheduled_runs(
|
123
|
+
v, PREFECT_DEPLOYMENT_SCHEDULE_MAX_SCHEDULED_RUNS.value()
|
124
|
+
)
|
104
125
|
|
105
126
|
|
106
127
|
class DeploymentScheduleUpdate(ActionBaseModel):
|
@@ -111,6 +132,27 @@ class DeploymentScheduleUpdate(ActionBaseModel):
|
|
111
132
|
default=True, description="Whether or not the schedule is active."
|
112
133
|
)
|
113
134
|
|
135
|
+
max_active_runs: Optional[PositiveInteger] = Field(
|
136
|
+
default=None,
|
137
|
+
description="The maximum number of active runs for the schedule.",
|
138
|
+
)
|
139
|
+
|
140
|
+
max_scheduled_runs: Optional[PositiveInteger] = Field(
|
141
|
+
default=None,
|
142
|
+
description="The maximum number of scheduled runs for the schedule.",
|
143
|
+
)
|
144
|
+
|
145
|
+
catchup: Optional[bool] = Field(
|
146
|
+
default=None,
|
147
|
+
description="Whether or not a worker should catch up on Late runs for the schedule.",
|
148
|
+
)
|
149
|
+
|
150
|
+
@validator("max_scheduled_runs")
|
151
|
+
def validate_max_scheduled_runs(cls, v):
|
152
|
+
return validate_schedule_max_scheduled_runs(
|
153
|
+
v, PREFECT_DEPLOYMENT_SCHEDULE_MAX_SCHEDULED_RUNS.value()
|
154
|
+
)
|
155
|
+
|
114
156
|
|
115
157
|
class DeploymentCreate(DeprecatedInfraOverridesField, ActionBaseModel):
|
116
158
|
"""Data used by the Prefect REST API to create a deployment."""
|
@@ -704,7 +746,7 @@ class GlobalConcurrencyLimitCreate(ActionBaseModel):
|
|
704
746
|
"""Data used by the Prefect REST API to create a global concurrency limit."""
|
705
747
|
|
706
748
|
name: str = Field(description="The name of the global concurrency limit.")
|
707
|
-
limit:
|
749
|
+
limit: NonNegativeInteger = Field(
|
708
750
|
description=(
|
709
751
|
"The maximum number of slots that can be occupied on this concurrency"
|
710
752
|
" limit."
|
@@ -714,11 +756,11 @@ class GlobalConcurrencyLimitCreate(ActionBaseModel):
|
|
714
756
|
default=True,
|
715
757
|
description="Whether or not the concurrency limit is in an active state.",
|
716
758
|
)
|
717
|
-
active_slots: Optional[
|
759
|
+
active_slots: Optional[NonNegativeInteger] = Field(
|
718
760
|
default=0,
|
719
761
|
description="Number of tasks currently using a concurrency slot.",
|
720
762
|
)
|
721
|
-
slot_decay_per_second: Optional[
|
763
|
+
slot_decay_per_second: Optional[NonNegativeFloat] = Field(
|
722
764
|
default=0.0,
|
723
765
|
description=(
|
724
766
|
"Controls the rate at which slots are released when the concurrency limit"
|
@@ -726,12 +768,20 @@ class GlobalConcurrencyLimitCreate(ActionBaseModel):
|
|
726
768
|
),
|
727
769
|
)
|
728
770
|
|
771
|
+
@validator("name", check_fields=False)
|
772
|
+
def validate_name_characters(cls, v):
|
773
|
+
return raise_on_name_with_banned_characters(v)
|
774
|
+
|
729
775
|
|
730
776
|
class GlobalConcurrencyLimitUpdate(ActionBaseModel):
|
731
777
|
"""Data used by the Prefect REST API to update a global concurrency limit."""
|
732
778
|
|
733
779
|
name: Optional[str] = Field(None)
|
734
|
-
limit: Optional[
|
735
|
-
active: Optional[
|
736
|
-
active_slots: Optional[
|
737
|
-
slot_decay_per_second: Optional[
|
780
|
+
limit: Optional[NonNegativeInteger] = Field(None)
|
781
|
+
active: Optional[NonNegativeInteger] = Field(None)
|
782
|
+
active_slots: Optional[NonNegativeInteger] = Field(None)
|
783
|
+
slot_decay_per_second: Optional[NonNegativeFloat] = Field(None)
|
784
|
+
|
785
|
+
@validator("name", check_fields=False)
|
786
|
+
def validate_name_characters(cls, v):
|
787
|
+
return raise_on_name_with_banned_characters(v)
|
@@ -690,7 +690,9 @@ class TaskRun(ObjectBaseModel):
|
|
690
690
|
task_inputs: Dict[str, List[Union[TaskRunResult, Parameter, Constant]]] = Field(
|
691
691
|
default_factory=dict,
|
692
692
|
description=(
|
693
|
-
"Tracks the source of inputs to a task run. Used for internal bookkeeping."
|
693
|
+
"Tracks the source of inputs to a task run. Used for internal bookkeeping. "
|
694
|
+
"Note the special __parents__ key, used to indicate a parent/child "
|
695
|
+
"relationship that may or may not include an input or wait_for semantic."
|
694
696
|
),
|
695
697
|
)
|
696
698
|
state_type: Optional[StateType] = Field(
|
@@ -931,6 +933,18 @@ class DeploymentSchedule(ObjectBaseModel):
|
|
931
933
|
active: bool = Field(
|
932
934
|
default=True, description="Whether or not the schedule is active."
|
933
935
|
)
|
936
|
+
max_active_runs: Optional[PositiveInteger] = Field(
|
937
|
+
default=None,
|
938
|
+
description="The maximum number of active runs for the schedule.",
|
939
|
+
)
|
940
|
+
max_scheduled_runs: Optional[PositiveInteger] = Field(
|
941
|
+
default=None,
|
942
|
+
description="The maximum number of scheduled runs for the schedule.",
|
943
|
+
)
|
944
|
+
catchup: bool = Field(
|
945
|
+
default=False,
|
946
|
+
description="Whether or not a worker should catch up on Late runs for the schedule.",
|
947
|
+
)
|
934
948
|
|
935
949
|
|
936
950
|
class Deployment(DeprecatedInfraOverridesField, ObjectBaseModel):
|
@@ -429,3 +429,22 @@ class MinimalConcurrencyLimitResponse(PrefectBaseModel):
|
|
429
429
|
id: UUID
|
430
430
|
name: str
|
431
431
|
limit: int
|
432
|
+
|
433
|
+
|
434
|
+
class GlobalConcurrencyLimitResponse(ObjectBaseModel):
|
435
|
+
"""
|
436
|
+
A response object for global concurrency limits.
|
437
|
+
"""
|
438
|
+
|
439
|
+
active: bool = Field(
|
440
|
+
default=True, description="Whether the global concurrency limit is active."
|
441
|
+
)
|
442
|
+
name: str = Field(
|
443
|
+
default=..., description="The name of the global concurrency limit."
|
444
|
+
)
|
445
|
+
limit: int = Field(default=..., description="The concurrency limit.")
|
446
|
+
active_slots: int = Field(default=..., description="The number of active slots.")
|
447
|
+
slot_decay_per_second: float = Field(
|
448
|
+
default=2.0,
|
449
|
+
description="The decay rate for active slots when used as a rate limit.",
|
450
|
+
)
|
@@ -65,7 +65,7 @@ class IntervalSchedule(PrefectBaseModel):
|
|
65
65
|
exclude_none = True
|
66
66
|
|
67
67
|
interval: PositiveDuration
|
68
|
-
anchor_date: DateTimeTZ = None
|
68
|
+
anchor_date: Optional[DateTimeTZ] = None
|
69
69
|
timezone: Optional[str] = Field(default=None, examples=["America/New_York"])
|
70
70
|
|
71
71
|
@validator("anchor_date", always=True)
|
prefect/client/utilities.py
CHANGED
@@ -49,12 +49,12 @@ def get_or_create_client(
|
|
49
49
|
|
50
50
|
if (
|
51
51
|
flow_run_context
|
52
|
-
and getattr(flow_run_context.client, "_loop") == get_running_loop()
|
52
|
+
and getattr(flow_run_context.client, "_loop", None) == get_running_loop()
|
53
53
|
):
|
54
54
|
return flow_run_context.client, True
|
55
55
|
elif (
|
56
56
|
task_run_context
|
57
|
-
and getattr(task_run_context.client, "_loop") == get_running_loop()
|
57
|
+
and getattr(task_run_context.client, "_loop", None) == get_running_loop()
|
58
58
|
):
|
59
59
|
return task_run_context.client, True
|
60
60
|
else:
|
prefect/concurrency/asyncio.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import asyncio
|
2
2
|
from contextlib import asynccontextmanager
|
3
|
-
from typing import List, Literal, Union, cast
|
3
|
+
from typing import List, Literal, Optional, Union, cast
|
4
4
|
|
5
5
|
import httpx
|
6
6
|
import pendulum
|
@@ -13,6 +13,7 @@ except ImportError:
|
|
13
13
|
|
14
14
|
from prefect import get_client
|
15
15
|
from prefect.client.schemas.responses import MinimalConcurrencyLimitResponse
|
16
|
+
from prefect.utilities.timeout import timeout_async
|
16
17
|
|
17
18
|
from .events import (
|
18
19
|
_emit_concurrency_acquisition_events,
|
@@ -26,10 +27,39 @@ class ConcurrencySlotAcquisitionError(Exception):
|
|
26
27
|
|
27
28
|
|
28
29
|
@asynccontextmanager
|
29
|
-
async def concurrency(
|
30
|
-
names
|
30
|
+
async def concurrency(
|
31
|
+
names: Union[str, List[str]],
|
32
|
+
occupy: int = 1,
|
33
|
+
timeout_seconds: Optional[float] = None,
|
34
|
+
):
|
35
|
+
"""A context manager that acquires and releases concurrency slots from the
|
36
|
+
given concurrency limits.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
names: The names of the concurrency limits to acquire slots from.
|
40
|
+
occupy: The number of slots to acquire and hold from each limit.
|
41
|
+
timeout_seconds: The number of seconds to wait for the slots to be acquired before
|
42
|
+
raising a `TimeoutError`. A timeout of `None` will wait indefinitely.
|
31
43
|
|
32
|
-
|
44
|
+
Raises:
|
45
|
+
TimeoutError: If the slots are not acquired within the given timeout.
|
46
|
+
|
47
|
+
Example:
|
48
|
+
A simple example of using the async `concurrency` context manager:
|
49
|
+
```python
|
50
|
+
from prefect.concurrency.asyncio import concurrency
|
51
|
+
|
52
|
+
async def resource_heavy():
|
53
|
+
async with concurrency("test", occupy=1):
|
54
|
+
print("Resource heavy task")
|
55
|
+
|
56
|
+
async def main():
|
57
|
+
await resource_heavy()
|
58
|
+
```
|
59
|
+
"""
|
60
|
+
names = names if isinstance(names, list) else [names]
|
61
|
+
with timeout_async(seconds=timeout_seconds):
|
62
|
+
limits = await _acquire_concurrency_slots(names, occupy)
|
33
63
|
acquisition_time = pendulum.now("UTC")
|
34
64
|
emitted_events = _emit_concurrency_acquisition_events(limits, occupy)
|
35
65
|
|
prefect/concurrency/sync.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from contextlib import contextmanager
|
2
|
-
from typing import List, Union, cast
|
2
|
+
from typing import List, Optional, Union, cast
|
3
3
|
|
4
4
|
import pendulum
|
5
5
|
|
@@ -12,6 +12,7 @@ except ImportError:
|
|
12
12
|
from prefect._internal.concurrency.api import create_call, from_sync
|
13
13
|
from prefect._internal.concurrency.event_loop import get_running_loop
|
14
14
|
from prefect.client.schemas.responses import MinimalConcurrencyLimitResponse
|
15
|
+
from prefect.utilities.timeout import timeout
|
15
16
|
|
16
17
|
from .asyncio import (
|
17
18
|
_acquire_concurrency_slots,
|
@@ -24,12 +25,42 @@ from .events import (
|
|
24
25
|
|
25
26
|
|
26
27
|
@contextmanager
|
27
|
-
def concurrency(
|
28
|
+
def concurrency(
|
29
|
+
names: Union[str, List[str]],
|
30
|
+
occupy: int = 1,
|
31
|
+
timeout_seconds: Optional[float] = None,
|
32
|
+
):
|
33
|
+
"""A context manager that acquires and releases concurrency slots from the
|
34
|
+
given concurrency limits.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
names: The names of the concurrency limits to acquire slots from.
|
38
|
+
occupy: The number of slots to acquire and hold from each limit.
|
39
|
+
timeout_seconds: The number of seconds to wait for the slots to be acquired before
|
40
|
+
raising a `TimeoutError`. A timeout of `None` will wait indefinitely.
|
41
|
+
|
42
|
+
Raises:
|
43
|
+
TimeoutError: If the slots are not acquired within the given timeout.
|
44
|
+
|
45
|
+
Example:
|
46
|
+
A simple example of using the sync `concurrency` context manager:
|
47
|
+
```python
|
48
|
+
from prefect.concurrency.sync import concurrency
|
49
|
+
|
50
|
+
def resource_heavy():
|
51
|
+
with concurrency("test", occupy=1):
|
52
|
+
print("Resource heavy task")
|
53
|
+
|
54
|
+
def main():
|
55
|
+
resource_heavy()
|
56
|
+
```
|
57
|
+
"""
|
28
58
|
names = names if isinstance(names, list) else [names]
|
29
59
|
|
30
|
-
|
31
|
-
|
32
|
-
|
60
|
+
with timeout(seconds=timeout_seconds):
|
61
|
+
limits: List[MinimalConcurrencyLimitResponse] = _call_async_function_from_sync(
|
62
|
+
_acquire_concurrency_slots, names, occupy
|
63
|
+
)
|
33
64
|
acquisition_time = pendulum.now("UTC")
|
34
65
|
emitted_events = _emit_concurrency_acquisition_events(limits, occupy)
|
35
66
|
|
@@ -38,7 +69,10 @@ def concurrency(names: Union[str, List[str]], occupy: int = 1):
|
|
38
69
|
finally:
|
39
70
|
occupancy_period = cast(Interval, pendulum.now("UTC") - acquisition_time)
|
40
71
|
_call_async_function_from_sync(
|
41
|
-
_release_concurrency_slots,
|
72
|
+
_release_concurrency_slots,
|
73
|
+
names,
|
74
|
+
occupy,
|
75
|
+
occupancy_period.total_seconds(),
|
42
76
|
)
|
43
77
|
_emit_concurrency_release_events(limits, occupy, emitted_events)
|
44
78
|
|
prefect/context.py
CHANGED
@@ -43,7 +43,7 @@ import prefect.logging
|
|
43
43
|
import prefect.logging.configuration
|
44
44
|
import prefect.settings
|
45
45
|
from prefect._internal.schemas.fields import DateTimeTZ
|
46
|
-
from prefect.client.orchestration import PrefectClient
|
46
|
+
from prefect.client.orchestration import PrefectClient, SyncPrefectClient
|
47
47
|
from prefect.client.schemas import FlowRun, TaskRun
|
48
48
|
from prefect.events.worker import EventsWorker
|
49
49
|
from prefect.exceptions import MissingContextError
|
@@ -213,7 +213,7 @@ class RunContext(ContextModel):
|
|
213
213
|
|
214
214
|
start_time: DateTimeTZ = Field(default_factory=lambda: pendulum.now("UTC"))
|
215
215
|
input_keyset: Optional[Dict[str, Dict[str, str]]] = None
|
216
|
-
client: PrefectClient
|
216
|
+
client: Union[PrefectClient, SyncPrefectClient]
|
217
217
|
|
218
218
|
|
219
219
|
class EngineContext(RunContext):
|
prefect/engine.py
CHANGED
@@ -163,6 +163,7 @@ from prefect.logging.loggers import (
|
|
163
163
|
from prefect.results import ResultFactory, UnknownResult
|
164
164
|
from prefect.settings import (
|
165
165
|
PREFECT_DEBUG_MODE,
|
166
|
+
PREFECT_EXPERIMENTAL_ENABLE_NEW_ENGINE,
|
166
167
|
PREFECT_TASK_INTROSPECTION_WARN_THRESHOLD,
|
167
168
|
PREFECT_TASKS_REFRESH_CACHE,
|
168
169
|
PREFECT_UI_URL,
|
@@ -189,6 +190,7 @@ from prefect.utilities.annotations import allow_failure, quote, unmapped
|
|
189
190
|
from prefect.utilities.asyncutils import (
|
190
191
|
gather,
|
191
192
|
is_async_fn,
|
193
|
+
run_sync,
|
192
194
|
sync_compatible,
|
193
195
|
)
|
194
196
|
from prefect.utilities.callables import (
|
@@ -2433,7 +2435,21 @@ if __name__ == "__main__":
|
|
2433
2435
|
exit(1)
|
2434
2436
|
|
2435
2437
|
try:
|
2436
|
-
|
2438
|
+
if PREFECT_EXPERIMENTAL_ENABLE_NEW_ENGINE.value():
|
2439
|
+
from prefect.new_flow_engine import (
|
2440
|
+
load_flow_and_flow_run,
|
2441
|
+
run_flow_async,
|
2442
|
+
run_flow_sync,
|
2443
|
+
)
|
2444
|
+
|
2445
|
+
flow_run, flow = run_sync(load_flow_and_flow_run)
|
2446
|
+
# run the flow
|
2447
|
+
if flow.isasync:
|
2448
|
+
run_sync(run_flow_async(flow, flow_run=flow_run))
|
2449
|
+
else:
|
2450
|
+
run_flow_sync(flow, flow_run=flow_run)
|
2451
|
+
else:
|
2452
|
+
enter_flow_run_engine_from_subprocess(flow_run_id)
|
2437
2453
|
except Abort as exc:
|
2438
2454
|
engine_logger.info(
|
2439
2455
|
f"Engine execution of flow run '{flow_run_id}' aborted by orchestrator:"
|
prefect/events/clients.py
CHANGED
@@ -28,7 +28,7 @@ from websockets.exceptions import (
|
|
28
28
|
ConnectionClosedOK,
|
29
29
|
)
|
30
30
|
|
31
|
-
from prefect.client.base import
|
31
|
+
from prefect.client.base import PrefectHttpxAsyncClient
|
32
32
|
from prefect.events import Event
|
33
33
|
from prefect.logging import get_logger
|
34
34
|
from prefect.settings import (
|
@@ -193,7 +193,7 @@ class PrefectEphemeralEventsClient(EventsClient):
|
|
193
193
|
|
194
194
|
app = create_app()
|
195
195
|
|
196
|
-
self._http_client =
|
196
|
+
self._http_client = PrefectHttpxAsyncClient(
|
197
197
|
transport=httpx.ASGITransport(app=app, raise_app_exceptions=False),
|
198
198
|
base_url="http://ephemeral-prefect/api",
|
199
199
|
enable_csrf_support=False,
|
prefect/flows.py
CHANGED
@@ -343,23 +343,6 @@ class Flow(Generic[P, R]):
|
|
343
343
|
self.result_storage = result_storage
|
344
344
|
self.result_serializer = result_serializer
|
345
345
|
self.cache_result_in_memory = cache_result_in_memory
|
346
|
-
|
347
|
-
# Check for collision in the registry
|
348
|
-
registry = PrefectObjectRegistry.get()
|
349
|
-
|
350
|
-
if registry and any(
|
351
|
-
other
|
352
|
-
for other in registry.get_instances(Flow)
|
353
|
-
if other.name == self.name and id(other.fn) != id(self.fn)
|
354
|
-
):
|
355
|
-
file = inspect.getsourcefile(self.fn)
|
356
|
-
line_number = inspect.getsourcelines(self.fn)[1]
|
357
|
-
warnings.warn(
|
358
|
-
f"A flow named {self.name!r} and defined at '{file}:{line_number}' "
|
359
|
-
"conflicts with another flow. Consider specifying a unique `name` "
|
360
|
-
"parameter in the flow definition:\n\n "
|
361
|
-
"`@flow(name='my_unique_name', ...)`"
|
362
|
-
)
|
363
346
|
self.on_completion = on_completion
|
364
347
|
self.on_failure = on_failure
|
365
348
|
self.on_cancellation = on_cancellation
|
@@ -1719,3 +1702,94 @@ def load_flow_from_text(script_contents: AnyStr, flow_name: str):
|
|
1719
1702
|
tmpfile.close()
|
1720
1703
|
os.remove(tmpfile.name)
|
1721
1704
|
return flow
|
1705
|
+
|
1706
|
+
|
1707
|
+
@sync_compatible
|
1708
|
+
async def serve(
|
1709
|
+
*args: "RunnerDeployment",
|
1710
|
+
pause_on_shutdown: bool = True,
|
1711
|
+
print_starting_message: bool = True,
|
1712
|
+
limit: Optional[int] = None,
|
1713
|
+
**kwargs,
|
1714
|
+
):
|
1715
|
+
"""
|
1716
|
+
Serve the provided list of deployments.
|
1717
|
+
|
1718
|
+
Args:
|
1719
|
+
*args: A list of deployments to serve.
|
1720
|
+
pause_on_shutdown: A boolean for whether or not to automatically pause
|
1721
|
+
deployment schedules on shutdown.
|
1722
|
+
print_starting_message: Whether or not to print message to the console
|
1723
|
+
on startup.
|
1724
|
+
limit: The maximum number of runs that can be executed concurrently.
|
1725
|
+
**kwargs: Additional keyword arguments to pass to the runner.
|
1726
|
+
|
1727
|
+
Examples:
|
1728
|
+
Prepare two deployments and serve them:
|
1729
|
+
|
1730
|
+
```python
|
1731
|
+
import datetime
|
1732
|
+
|
1733
|
+
from prefect import flow, serve
|
1734
|
+
|
1735
|
+
@flow
|
1736
|
+
def my_flow(name):
|
1737
|
+
print(f"hello {name}")
|
1738
|
+
|
1739
|
+
@flow
|
1740
|
+
def my_other_flow(name):
|
1741
|
+
print(f"goodbye {name}")
|
1742
|
+
|
1743
|
+
if __name__ == "__main__":
|
1744
|
+
# Run once a day
|
1745
|
+
hello_deploy = my_flow.to_deployment(
|
1746
|
+
"hello", tags=["dev"], interval=datetime.timedelta(days=1)
|
1747
|
+
)
|
1748
|
+
|
1749
|
+
# Run every Sunday at 4:00 AM
|
1750
|
+
bye_deploy = my_other_flow.to_deployment(
|
1751
|
+
"goodbye", tags=["dev"], cron="0 4 * * sun"
|
1752
|
+
)
|
1753
|
+
|
1754
|
+
serve(hello_deploy, bye_deploy)
|
1755
|
+
```
|
1756
|
+
"""
|
1757
|
+
from rich.console import Console, Group
|
1758
|
+
from rich.table import Table
|
1759
|
+
|
1760
|
+
from prefect.runner import Runner
|
1761
|
+
|
1762
|
+
runner = Runner(pause_on_shutdown=pause_on_shutdown, limit=limit, **kwargs)
|
1763
|
+
for deployment in args:
|
1764
|
+
await runner.add_deployment(deployment)
|
1765
|
+
|
1766
|
+
if print_starting_message:
|
1767
|
+
help_message_top = (
|
1768
|
+
"[green]Your deployments are being served and polling for"
|
1769
|
+
" scheduled runs!\n[/]"
|
1770
|
+
)
|
1771
|
+
|
1772
|
+
table = Table(title="Deployments", show_header=False)
|
1773
|
+
|
1774
|
+
table.add_column(style="blue", no_wrap=True)
|
1775
|
+
|
1776
|
+
for deployment in args:
|
1777
|
+
table.add_row(f"{deployment.flow_name}/{deployment.name}")
|
1778
|
+
|
1779
|
+
help_message_bottom = (
|
1780
|
+
"\nTo trigger any of these deployments, use the"
|
1781
|
+
" following command:\n[blue]\n\t$ prefect deployment run"
|
1782
|
+
" [DEPLOYMENT_NAME]\n[/]"
|
1783
|
+
)
|
1784
|
+
if PREFECT_UI_URL:
|
1785
|
+
help_message_bottom += (
|
1786
|
+
"\nYou can also trigger your deployments via the Prefect UI:"
|
1787
|
+
f" [blue]{PREFECT_UI_URL.value()}/deployments[/]\n"
|
1788
|
+
)
|
1789
|
+
|
1790
|
+
console = Console()
|
1791
|
+
console.print(
|
1792
|
+
Group(help_message_top, table, help_message_bottom), soft_wrap=True
|
1793
|
+
)
|
1794
|
+
|
1795
|
+
await runner.start()
|
@@ -7,7 +7,6 @@ It has been replaced by the process worker from the `prefect.workers` module, wh
|
|
7
7
|
For upgrade instructions, see https://docs.prefect.io/latest/guides/upgrade-guide-agents-to-workers/.
|
8
8
|
"""
|
9
9
|
|
10
|
-
import asyncio
|
11
10
|
import contextlib
|
12
11
|
import os
|
13
12
|
import shlex
|
@@ -21,7 +20,6 @@ from typing import Dict, Tuple, Union
|
|
21
20
|
|
22
21
|
import anyio
|
23
22
|
import anyio.abc
|
24
|
-
import sniffio
|
25
23
|
|
26
24
|
from prefect._internal.compatibility.deprecated import deprecated_class
|
27
25
|
from prefect._internal.pydantic import HAS_PYDANTIC_V2
|
@@ -43,20 +41,6 @@ if sys.platform == "win32":
|
|
43
41
|
STATUS_CONTROL_C_EXIT = 0xC000013A
|
44
42
|
|
45
43
|
|
46
|
-
def _use_threaded_child_watcher():
|
47
|
-
if (
|
48
|
-
sys.version_info < (3, 8)
|
49
|
-
and sniffio.current_async_library() == "asyncio"
|
50
|
-
and sys.platform != "win32"
|
51
|
-
):
|
52
|
-
from prefect.utilities.compat import ThreadedChildWatcher
|
53
|
-
|
54
|
-
# Python < 3.8 does not use a `ThreadedChildWatcher` by default which can
|
55
|
-
# lead to errors in tests on unix as the previous default `SafeChildWatcher`
|
56
|
-
# is not compatible with threaded event loops.
|
57
|
-
asyncio.get_event_loop_policy().set_child_watcher(ThreadedChildWatcher())
|
58
|
-
|
59
|
-
|
60
44
|
def _infrastructure_pid_from_process(process: anyio.abc.Process) -> str:
|
61
45
|
hostname = socket.gethostname()
|
62
46
|
return f"{hostname}:{process.pid}"
|
@@ -121,7 +105,6 @@ class Process(Infrastructure):
|
|
121
105
|
if not self.command:
|
122
106
|
raise ValueError("Process cannot be run with empty command.")
|
123
107
|
|
124
|
-
_use_threaded_child_watcher()
|
125
108
|
display_name = f" {self.name!r}" if self.name else ""
|
126
109
|
|
127
110
|
# Open a subprocess to execute the flow run
|
prefect/logging/formatters.py
CHANGED
@@ -99,10 +99,7 @@ class PrefectFormatter(logging.Formatter):
|
|
99
99
|
style_kwargs["defaults"] = defaults
|
100
100
|
|
101
101
|
# validate added in 3.8
|
102
|
-
|
103
|
-
init_kwargs["validate"] = validate
|
104
|
-
else:
|
105
|
-
validate = False
|
102
|
+
init_kwargs["validate"] = validate
|
106
103
|
|
107
104
|
super().__init__(format, datefmt, style, **init_kwargs)
|
108
105
|
|