prefect-client 2.20.4__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prefect/__init__.py +74 -110
- prefect/_internal/compatibility/deprecated.py +6 -115
- prefect/_internal/compatibility/experimental.py +4 -79
- prefect/_internal/compatibility/migration.py +166 -0
- prefect/_internal/concurrency/__init__.py +2 -2
- prefect/_internal/concurrency/api.py +1 -35
- prefect/_internal/concurrency/calls.py +0 -6
- prefect/_internal/concurrency/cancellation.py +0 -3
- prefect/_internal/concurrency/event_loop.py +0 -20
- prefect/_internal/concurrency/inspection.py +3 -3
- prefect/_internal/concurrency/primitives.py +1 -0
- prefect/_internal/concurrency/services.py +23 -0
- prefect/_internal/concurrency/threads.py +35 -0
- prefect/_internal/concurrency/waiters.py +0 -28
- prefect/_internal/integrations.py +7 -0
- prefect/_internal/pydantic/__init__.py +0 -45
- prefect/_internal/pydantic/annotations/pendulum.py +2 -2
- prefect/_internal/pydantic/v1_schema.py +21 -22
- prefect/_internal/pydantic/v2_schema.py +0 -2
- prefect/_internal/pydantic/v2_validated_func.py +18 -23
- prefect/_internal/pytz.py +1 -1
- prefect/_internal/retries.py +61 -0
- prefect/_internal/schemas/bases.py +45 -177
- prefect/_internal/schemas/fields.py +1 -43
- prefect/_internal/schemas/validators.py +47 -233
- prefect/agent.py +3 -695
- prefect/artifacts.py +173 -14
- prefect/automations.py +39 -4
- prefect/blocks/abstract.py +1 -1
- prefect/blocks/core.py +405 -153
- prefect/blocks/fields.py +2 -57
- prefect/blocks/notifications.py +43 -28
- prefect/blocks/redis.py +168 -0
- prefect/blocks/system.py +67 -20
- prefect/blocks/webhook.py +2 -9
- prefect/cache_policies.py +239 -0
- prefect/client/__init__.py +4 -0
- prefect/client/base.py +33 -27
- prefect/client/cloud.py +65 -20
- prefect/client/collections.py +1 -1
- prefect/client/orchestration.py +650 -442
- prefect/client/schemas/actions.py +115 -100
- prefect/client/schemas/filters.py +46 -52
- prefect/client/schemas/objects.py +228 -178
- prefect/client/schemas/responses.py +18 -36
- prefect/client/schemas/schedules.py +55 -36
- prefect/client/schemas/sorting.py +2 -0
- prefect/client/subscriptions.py +8 -7
- prefect/client/types/flexible_schedule_list.py +11 -0
- prefect/client/utilities.py +9 -6
- prefect/concurrency/asyncio.py +60 -11
- prefect/concurrency/context.py +24 -0
- prefect/concurrency/events.py +2 -2
- prefect/concurrency/services.py +46 -16
- prefect/concurrency/sync.py +51 -7
- prefect/concurrency/v1/asyncio.py +143 -0
- prefect/concurrency/v1/context.py +27 -0
- prefect/concurrency/v1/events.py +61 -0
- prefect/concurrency/v1/services.py +116 -0
- prefect/concurrency/v1/sync.py +92 -0
- prefect/context.py +246 -149
- prefect/deployments/__init__.py +33 -18
- prefect/deployments/base.py +10 -15
- prefect/deployments/deployments.py +2 -1048
- prefect/deployments/flow_runs.py +178 -0
- prefect/deployments/runner.py +72 -173
- prefect/deployments/schedules.py +31 -25
- prefect/deployments/steps/__init__.py +0 -1
- prefect/deployments/steps/core.py +7 -0
- prefect/deployments/steps/pull.py +15 -21
- prefect/deployments/steps/utility.py +2 -1
- prefect/docker/__init__.py +20 -0
- prefect/docker/docker_image.py +82 -0
- prefect/engine.py +15 -2475
- prefect/events/actions.py +17 -23
- prefect/events/cli/automations.py +20 -7
- prefect/events/clients.py +142 -80
- prefect/events/filters.py +14 -18
- prefect/events/related.py +74 -75
- prefect/events/schemas/__init__.py +0 -5
- prefect/events/schemas/automations.py +55 -46
- prefect/events/schemas/deployment_triggers.py +7 -197
- prefect/events/schemas/events.py +46 -65
- prefect/events/schemas/labelling.py +10 -14
- prefect/events/utilities.py +4 -5
- prefect/events/worker.py +23 -8
- prefect/exceptions.py +15 -0
- prefect/filesystems.py +30 -529
- prefect/flow_engine.py +827 -0
- prefect/flow_runs.py +379 -7
- prefect/flows.py +470 -360
- prefect/futures.py +382 -331
- prefect/infrastructure/__init__.py +5 -26
- prefect/infrastructure/base.py +3 -320
- prefect/infrastructure/provisioners/__init__.py +5 -3
- prefect/infrastructure/provisioners/cloud_run.py +13 -8
- prefect/infrastructure/provisioners/container_instance.py +14 -9
- prefect/infrastructure/provisioners/ecs.py +10 -8
- prefect/infrastructure/provisioners/modal.py +8 -5
- prefect/input/__init__.py +4 -0
- prefect/input/actions.py +2 -4
- prefect/input/run_input.py +9 -9
- prefect/logging/formatters.py +2 -4
- prefect/logging/handlers.py +9 -14
- prefect/logging/loggers.py +5 -5
- prefect/main.py +72 -0
- prefect/plugins.py +2 -64
- prefect/profiles.toml +16 -2
- prefect/records/__init__.py +1 -0
- prefect/records/base.py +223 -0
- prefect/records/filesystem.py +207 -0
- prefect/records/memory.py +178 -0
- prefect/records/result_store.py +64 -0
- prefect/results.py +577 -504
- prefect/runner/runner.py +117 -47
- prefect/runner/server.py +32 -34
- prefect/runner/storage.py +3 -12
- prefect/runner/submit.py +2 -10
- prefect/runner/utils.py +2 -2
- prefect/runtime/__init__.py +1 -0
- prefect/runtime/deployment.py +1 -0
- prefect/runtime/flow_run.py +40 -5
- prefect/runtime/task_run.py +1 -0
- prefect/serializers.py +28 -39
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
- prefect/settings.py +209 -332
- prefect/states.py +160 -63
- prefect/task_engine.py +1478 -57
- prefect/task_runners.py +383 -287
- prefect/task_runs.py +240 -0
- prefect/task_worker.py +463 -0
- prefect/tasks.py +684 -374
- prefect/transactions.py +410 -0
- prefect/types/__init__.py +72 -86
- prefect/types/entrypoint.py +13 -0
- prefect/utilities/annotations.py +4 -3
- prefect/utilities/asyncutils.py +227 -148
- prefect/utilities/callables.py +137 -45
- prefect/utilities/collections.py +134 -86
- prefect/utilities/dispatch.py +27 -14
- prefect/utilities/dockerutils.py +11 -4
- prefect/utilities/engine.py +186 -32
- prefect/utilities/filesystem.py +4 -5
- prefect/utilities/importtools.py +26 -27
- prefect/utilities/pydantic.py +128 -38
- prefect/utilities/schema_tools/hydration.py +18 -1
- prefect/utilities/schema_tools/validation.py +30 -0
- prefect/utilities/services.py +35 -9
- prefect/utilities/templating.py +12 -2
- prefect/utilities/timeout.py +20 -5
- prefect/utilities/urls.py +195 -0
- prefect/utilities/visualization.py +1 -0
- prefect/variables.py +78 -59
- prefect/workers/__init__.py +0 -1
- prefect/workers/base.py +237 -244
- prefect/workers/block.py +5 -226
- prefect/workers/cloud.py +6 -0
- prefect/workers/process.py +265 -12
- prefect/workers/server.py +29 -11
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/METADATA +28 -24
- prefect_client-3.0.0.dist-info/RECORD +201 -0
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/WHEEL +1 -1
- prefect/_internal/pydantic/_base_model.py +0 -51
- prefect/_internal/pydantic/_compat.py +0 -82
- prefect/_internal/pydantic/_flags.py +0 -20
- prefect/_internal/pydantic/_types.py +0 -8
- prefect/_internal/pydantic/utilities/config_dict.py +0 -72
- prefect/_internal/pydantic/utilities/field_validator.py +0 -150
- prefect/_internal/pydantic/utilities/model_construct.py +0 -56
- prefect/_internal/pydantic/utilities/model_copy.py +0 -55
- prefect/_internal/pydantic/utilities/model_dump.py +0 -136
- prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
- prefect/_internal/pydantic/utilities/model_fields.py +0 -50
- prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
- prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
- prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
- prefect/_internal/pydantic/utilities/model_validate.py +0 -75
- prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
- prefect/_internal/pydantic/utilities/model_validator.py +0 -87
- prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
- prefect/_vendor/fastapi/__init__.py +0 -25
- prefect/_vendor/fastapi/applications.py +0 -946
- prefect/_vendor/fastapi/background.py +0 -3
- prefect/_vendor/fastapi/concurrency.py +0 -44
- prefect/_vendor/fastapi/datastructures.py +0 -58
- prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
- prefect/_vendor/fastapi/dependencies/models.py +0 -64
- prefect/_vendor/fastapi/dependencies/utils.py +0 -877
- prefect/_vendor/fastapi/encoders.py +0 -177
- prefect/_vendor/fastapi/exception_handlers.py +0 -40
- prefect/_vendor/fastapi/exceptions.py +0 -46
- prefect/_vendor/fastapi/logger.py +0 -3
- prefect/_vendor/fastapi/middleware/__init__.py +0 -1
- prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
- prefect/_vendor/fastapi/middleware/cors.py +0 -3
- prefect/_vendor/fastapi/middleware/gzip.py +0 -3
- prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
- prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
- prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
- prefect/_vendor/fastapi/openapi/__init__.py +0 -0
- prefect/_vendor/fastapi/openapi/constants.py +0 -2
- prefect/_vendor/fastapi/openapi/docs.py +0 -203
- prefect/_vendor/fastapi/openapi/models.py +0 -480
- prefect/_vendor/fastapi/openapi/utils.py +0 -485
- prefect/_vendor/fastapi/param_functions.py +0 -340
- prefect/_vendor/fastapi/params.py +0 -453
- prefect/_vendor/fastapi/py.typed +0 -0
- prefect/_vendor/fastapi/requests.py +0 -4
- prefect/_vendor/fastapi/responses.py +0 -40
- prefect/_vendor/fastapi/routing.py +0 -1331
- prefect/_vendor/fastapi/security/__init__.py +0 -15
- prefect/_vendor/fastapi/security/api_key.py +0 -98
- prefect/_vendor/fastapi/security/base.py +0 -6
- prefect/_vendor/fastapi/security/http.py +0 -172
- prefect/_vendor/fastapi/security/oauth2.py +0 -227
- prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
- prefect/_vendor/fastapi/security/utils.py +0 -10
- prefect/_vendor/fastapi/staticfiles.py +0 -1
- prefect/_vendor/fastapi/templating.py +0 -3
- prefect/_vendor/fastapi/testclient.py +0 -1
- prefect/_vendor/fastapi/types.py +0 -3
- prefect/_vendor/fastapi/utils.py +0 -235
- prefect/_vendor/fastapi/websockets.py +0 -7
- prefect/_vendor/starlette/__init__.py +0 -1
- prefect/_vendor/starlette/_compat.py +0 -28
- prefect/_vendor/starlette/_exception_handler.py +0 -80
- prefect/_vendor/starlette/_utils.py +0 -88
- prefect/_vendor/starlette/applications.py +0 -261
- prefect/_vendor/starlette/authentication.py +0 -159
- prefect/_vendor/starlette/background.py +0 -43
- prefect/_vendor/starlette/concurrency.py +0 -59
- prefect/_vendor/starlette/config.py +0 -151
- prefect/_vendor/starlette/convertors.py +0 -87
- prefect/_vendor/starlette/datastructures.py +0 -707
- prefect/_vendor/starlette/endpoints.py +0 -130
- prefect/_vendor/starlette/exceptions.py +0 -60
- prefect/_vendor/starlette/formparsers.py +0 -276
- prefect/_vendor/starlette/middleware/__init__.py +0 -17
- prefect/_vendor/starlette/middleware/authentication.py +0 -52
- prefect/_vendor/starlette/middleware/base.py +0 -220
- prefect/_vendor/starlette/middleware/cors.py +0 -176
- prefect/_vendor/starlette/middleware/errors.py +0 -265
- prefect/_vendor/starlette/middleware/exceptions.py +0 -74
- prefect/_vendor/starlette/middleware/gzip.py +0 -113
- prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
- prefect/_vendor/starlette/middleware/sessions.py +0 -82
- prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
- prefect/_vendor/starlette/middleware/wsgi.py +0 -147
- prefect/_vendor/starlette/py.typed +0 -0
- prefect/_vendor/starlette/requests.py +0 -328
- prefect/_vendor/starlette/responses.py +0 -347
- prefect/_vendor/starlette/routing.py +0 -933
- prefect/_vendor/starlette/schemas.py +0 -154
- prefect/_vendor/starlette/staticfiles.py +0 -248
- prefect/_vendor/starlette/status.py +0 -199
- prefect/_vendor/starlette/templating.py +0 -231
- prefect/_vendor/starlette/testclient.py +0 -804
- prefect/_vendor/starlette/types.py +0 -30
- prefect/_vendor/starlette/websockets.py +0 -193
- prefect/blocks/kubernetes.py +0 -119
- prefect/deprecated/__init__.py +0 -0
- prefect/deprecated/data_documents.py +0 -350
- prefect/deprecated/packaging/__init__.py +0 -12
- prefect/deprecated/packaging/base.py +0 -96
- prefect/deprecated/packaging/docker.py +0 -146
- prefect/deprecated/packaging/file.py +0 -92
- prefect/deprecated/packaging/orion.py +0 -80
- prefect/deprecated/packaging/serializers.py +0 -171
- prefect/events/instrument.py +0 -135
- prefect/infrastructure/container.py +0 -824
- prefect/infrastructure/kubernetes.py +0 -920
- prefect/infrastructure/process.py +0 -289
- prefect/manifests.py +0 -20
- prefect/new_flow_engine.py +0 -449
- prefect/new_task_engine.py +0 -423
- prefect/pydantic/__init__.py +0 -76
- prefect/pydantic/main.py +0 -39
- prefect/software/__init__.py +0 -2
- prefect/software/base.py +0 -50
- prefect/software/conda.py +0 -199
- prefect/software/pip.py +0 -122
- prefect/software/python.py +0 -52
- prefect/task_server.py +0 -322
- prefect_client-2.20.4.dist-info/RECORD +0 -294
- /prefect/{_internal/pydantic/utilities → client/types}/__init__.py +0 -0
- /prefect/{_vendor → concurrency/v1}/__init__.py +0 -0
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,6 @@ import abc
|
|
6
6
|
import asyncio
|
7
7
|
import concurrent.futures
|
8
8
|
import contextlib
|
9
|
-
import threading
|
10
9
|
from typing import (
|
11
10
|
Awaitable,
|
12
11
|
Callable,
|
@@ -19,7 +18,6 @@ from typing import (
|
|
19
18
|
|
20
19
|
from typing_extensions import ParamSpec
|
21
20
|
|
22
|
-
from prefect._internal.concurrency.calls import get_current_call
|
23
21
|
from prefect._internal.concurrency.threads import (
|
24
22
|
WorkerThread,
|
25
23
|
get_global_loop,
|
@@ -29,7 +27,6 @@ from prefect._internal.concurrency.waiters import (
|
|
29
27
|
AsyncWaiter,
|
30
28
|
Call,
|
31
29
|
SyncWaiter,
|
32
|
-
get_waiter_for_thread,
|
33
30
|
)
|
34
31
|
|
35
32
|
P = ParamSpec("P")
|
@@ -154,7 +151,7 @@ class from_async(_base):
|
|
154
151
|
__call: Union[Callable[[], T], Call[T]],
|
155
152
|
timeout: Optional[float] = None,
|
156
153
|
done_callbacks: Optional[Iterable[Call]] = None,
|
157
|
-
) ->
|
154
|
+
) -> T:
|
158
155
|
call = _cast_to_call(__call)
|
159
156
|
waiter = AsyncWaiter(call=call)
|
160
157
|
for callback in done_callbacks or []:
|
@@ -163,22 +160,6 @@ class from_async(_base):
|
|
163
160
|
await waiter.wait()
|
164
161
|
return call.result()
|
165
162
|
|
166
|
-
@staticmethod
|
167
|
-
def call_soon_in_waiting_thread(
|
168
|
-
__call: Union[Callable[[], T], Call[T]],
|
169
|
-
thread: threading.Thread,
|
170
|
-
timeout: Optional[float] = None,
|
171
|
-
) -> Call[T]:
|
172
|
-
call = _cast_to_call(__call)
|
173
|
-
parent_call = get_current_call()
|
174
|
-
waiter = get_waiter_for_thread(thread, parent_call)
|
175
|
-
if waiter is None:
|
176
|
-
raise RuntimeError(f"No waiter found for thread {thread}.")
|
177
|
-
|
178
|
-
call.set_timeout(timeout)
|
179
|
-
waiter.submit(call)
|
180
|
-
return call
|
181
|
-
|
182
163
|
@staticmethod
|
183
164
|
def call_in_new_thread(
|
184
165
|
__call: Union[Callable[[], T], Call[T]], timeout: Optional[float] = None
|
@@ -231,21 +212,6 @@ class from_sync(_base):
|
|
231
212
|
waiter.wait()
|
232
213
|
return call.result()
|
233
214
|
|
234
|
-
@staticmethod
|
235
|
-
def call_soon_in_waiting_thread(
|
236
|
-
__call: Union[Callable[[], T], Call[T]],
|
237
|
-
thread: threading.Thread,
|
238
|
-
timeout: Optional[float] = None,
|
239
|
-
) -> Call[T]:
|
240
|
-
call = _cast_to_call(__call)
|
241
|
-
waiter = get_waiter_for_thread(thread)
|
242
|
-
if waiter is None:
|
243
|
-
raise RuntimeError(f"No waiter found for thread {thread}.")
|
244
|
-
|
245
|
-
call.set_timeout(timeout)
|
246
|
-
waiter.submit(call)
|
247
|
-
return call
|
248
|
-
|
249
215
|
@staticmethod
|
250
216
|
def call_in_new_thread(
|
251
217
|
__call: Union[Callable[[], T], Call[T]], timeout: Optional[float] = None
|
@@ -49,12 +49,6 @@ current_call: contextvars.ContextVar["weakref.ref[Call]"] = ( # novm
|
|
49
49
|
_ASYNC_TASK_REFS = set()
|
50
50
|
|
51
51
|
|
52
|
-
def get_current_call() -> Optional["Call"]:
|
53
|
-
call_ref = current_call.get(None)
|
54
|
-
if call_ref:
|
55
|
-
return call_ref()
|
56
|
-
|
57
|
-
|
58
52
|
@contextlib.contextmanager
|
59
53
|
def set_current_call(call: "Call"):
|
60
54
|
token = current_call.set(weakref.ref(call))
|
@@ -76,9 +76,6 @@ class ThreadShield:
|
|
76
76
|
|
77
77
|
|
78
78
|
class CancelledError(asyncio.CancelledError):
|
79
|
-
# In Python 3.7, `asyncio.CancelledError` is identical to `concurrent.futures.CancelledError`
|
80
|
-
# but in 3.8+ it is a separate class that inherits from `BaseException` instead
|
81
|
-
# See https://bugs.python.org/issue32528
|
82
79
|
# We want our `CancelledError` to be treated as a `BaseException` and defining it
|
83
80
|
# here simplifies downstream logic that needs to know "which" cancelled error to
|
84
81
|
# handle.
|
@@ -25,26 +25,6 @@ def get_running_loop() -> Optional[asyncio.BaseEventLoop]:
|
|
25
25
|
return None
|
26
26
|
|
27
27
|
|
28
|
-
def call_in_loop(
|
29
|
-
__loop: asyncio.AbstractEventLoop,
|
30
|
-
__fn: Callable[P, T],
|
31
|
-
*args: P.args,
|
32
|
-
**kwargs: P.kwargs,
|
33
|
-
) -> T:
|
34
|
-
"""
|
35
|
-
Run a synchronous call in event loop's thread from another thread.
|
36
|
-
|
37
|
-
This function is blocking and not safe to call from an asynchronous context.
|
38
|
-
|
39
|
-
Returns the result of the call.
|
40
|
-
"""
|
41
|
-
if __loop is get_running_loop():
|
42
|
-
return __fn(*args, **kwargs)
|
43
|
-
else:
|
44
|
-
future = call_soon_in_loop(__loop, __fn, *args, **kwargs)
|
45
|
-
return future.result()
|
46
|
-
|
47
|
-
|
48
28
|
def call_soon_in_loop(
|
49
29
|
__loop: asyncio.AbstractEventLoop,
|
50
30
|
__fn: Callable[P, T],
|
@@ -7,10 +7,10 @@ import linecache
|
|
7
7
|
import sys
|
8
8
|
import threading
|
9
9
|
from types import FrameType
|
10
|
-
from typing import List
|
10
|
+
from typing import List, Optional
|
11
11
|
|
12
12
|
"""
|
13
|
-
The following functions are derived from dask/distributed which is licensed under the
|
13
|
+
The following functions are derived from dask/distributed which is licensed under the
|
14
14
|
BSD 3-Clause License.
|
15
15
|
|
16
16
|
Copyright (c) 2015, Anaconda, Inc. and contributors
|
@@ -75,7 +75,7 @@ def repr_frame(frame: FrameType) -> str:
|
|
75
75
|
def call_stack(frame: FrameType) -> List[str]:
|
76
76
|
"""Create a call text stack from a frame"""
|
77
77
|
L = []
|
78
|
-
cur_frame: FrameType
|
78
|
+
cur_frame: Optional[FrameType] = frame
|
79
79
|
while cur_frame:
|
80
80
|
L.append(repr_frame(cur_frame))
|
81
81
|
cur_frame = cur_frame.f_back
|
@@ -39,6 +39,7 @@ class QueueService(abc.ABC, Generic[T]):
|
|
39
39
|
daemon=True,
|
40
40
|
name=f"{type(self).__name__}Thread",
|
41
41
|
)
|
42
|
+
self._logger = logging.getLogger(f"{type(self).__name__}")
|
42
43
|
|
43
44
|
def start(self):
|
44
45
|
logger.debug("Starting service %r", self)
|
@@ -144,13 +145,27 @@ class QueueService(abc.ABC, Generic[T]):
|
|
144
145
|
self._done_event.set()
|
145
146
|
|
146
147
|
async def _main_loop(self):
|
148
|
+
last_log_time = 0
|
149
|
+
log_interval = 4 # log every 4 seconds
|
150
|
+
|
147
151
|
while True:
|
148
152
|
item: T = await self._queue_get_thread.submit(
|
149
153
|
create_call(self._queue.get)
|
150
154
|
).aresult()
|
151
155
|
|
156
|
+
if self._stopped:
|
157
|
+
current_time = asyncio.get_event_loop().time()
|
158
|
+
queue_size = self._queue.qsize()
|
159
|
+
|
160
|
+
if current_time - last_log_time >= log_interval and queue_size > 0:
|
161
|
+
self._logger.warning(
|
162
|
+
f"Still processing items: {queue_size} items remaining..."
|
163
|
+
)
|
164
|
+
last_log_time = current_time
|
165
|
+
|
152
166
|
if item is None:
|
153
167
|
logger.debug("Exiting service %r", self)
|
168
|
+
self._queue.task_done()
|
154
169
|
break
|
155
170
|
|
156
171
|
try:
|
@@ -164,6 +179,8 @@ class QueueService(abc.ABC, Generic[T]):
|
|
164
179
|
item,
|
165
180
|
exc_info=log_traceback,
|
166
181
|
)
|
182
|
+
finally:
|
183
|
+
self._queue.task_done()
|
167
184
|
|
168
185
|
@abc.abstractmethod
|
169
186
|
async def _handle(self, item: T):
|
@@ -235,6 +252,12 @@ class QueueService(abc.ABC, Generic[T]):
|
|
235
252
|
else:
|
236
253
|
return concurrent.futures.wait(futures, timeout=timeout)
|
237
254
|
|
255
|
+
def wait_until_empty(self):
|
256
|
+
"""
|
257
|
+
Wait until the queue is empty and all items have been processed.
|
258
|
+
"""
|
259
|
+
self._queue.join()
|
260
|
+
|
238
261
|
@classmethod
|
239
262
|
def instance(cls: Type[Self], *args) -> Self:
|
240
263
|
"""
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""
|
2
2
|
Utilities for managing worker threads.
|
3
3
|
"""
|
4
|
+
|
4
5
|
import asyncio
|
5
6
|
import atexit
|
6
7
|
import concurrent.futures
|
@@ -233,7 +234,10 @@ class EventLoopThread(Portal):
|
|
233
234
|
self.shutdown()
|
234
235
|
|
235
236
|
|
237
|
+
# the GLOBAL LOOP is used for background services, like logs
|
236
238
|
GLOBAL_LOOP: Optional[EventLoopThread] = None
|
239
|
+
# the RUN SYNC LOOP is used exclusively for running async functions in a sync context via asyncutils.run_sync
|
240
|
+
RUN_SYNC_LOOP: Optional[EventLoopThread] = None
|
237
241
|
|
238
242
|
|
239
243
|
def get_global_loop() -> EventLoopThread:
|
@@ -267,6 +271,37 @@ def in_global_loop() -> bool:
|
|
267
271
|
return get_global_loop()._loop == get_running_loop()
|
268
272
|
|
269
273
|
|
274
|
+
def get_run_sync_loop() -> EventLoopThread:
|
275
|
+
"""
|
276
|
+
Get the run_sync loop thread.
|
277
|
+
|
278
|
+
Creates a new one if there is not one available.
|
279
|
+
"""
|
280
|
+
global RUN_SYNC_LOOP
|
281
|
+
|
282
|
+
# Create a new worker on first call or if the existing worker is dead
|
283
|
+
if (
|
284
|
+
RUN_SYNC_LOOP is None
|
285
|
+
or not RUN_SYNC_LOOP.thread.is_alive()
|
286
|
+
or RUN_SYNC_LOOP._shutdown_event.is_set()
|
287
|
+
):
|
288
|
+
RUN_SYNC_LOOP = EventLoopThread(daemon=True, name="RunSyncEventLoopThread")
|
289
|
+
RUN_SYNC_LOOP.start()
|
290
|
+
|
291
|
+
return RUN_SYNC_LOOP
|
292
|
+
|
293
|
+
|
294
|
+
def in_run_sync_loop() -> bool:
|
295
|
+
"""
|
296
|
+
Check if called from the global loop.
|
297
|
+
"""
|
298
|
+
if RUN_SYNC_LOOP is None:
|
299
|
+
# Avoid creating a global loop if there isn't one
|
300
|
+
return False
|
301
|
+
|
302
|
+
return get_run_sync_loop()._loop == get_running_loop()
|
303
|
+
|
304
|
+
|
270
305
|
def wait_for_global_loop_exit(timeout: Optional[float] = None) -> None:
|
271
306
|
"""
|
272
307
|
Shutdown the global loop and wait for it to exit.
|
@@ -29,34 +29,6 @@ _WAITERS_BY_THREAD: "WeakKeyDictionary[threading.Thread, deque[Waiter]]" = (
|
|
29
29
|
)
|
30
30
|
|
31
31
|
|
32
|
-
def get_waiter_for_thread(
|
33
|
-
thread: threading.Thread, parent_call: Optional[Call] = None
|
34
|
-
) -> Optional["Waiter"]:
|
35
|
-
"""
|
36
|
-
Get the current waiter for a thread and an optional parent call.
|
37
|
-
|
38
|
-
To avoid assigning outer callbacks to inner waiters in the case of nested calls,
|
39
|
-
the parent call is used to determine which waiter to return. If a parent call is
|
40
|
-
not provided, we return the most recently created waiter (last in the stack).
|
41
|
-
|
42
|
-
see https://github.com/PrefectHQ/prefect/issues/12036
|
43
|
-
|
44
|
-
Returns `None` if no active waiter is found for the thread.
|
45
|
-
"""
|
46
|
-
|
47
|
-
waiters: "Optional[deque[Waiter]]" = _WAITERS_BY_THREAD.get(thread)
|
48
|
-
|
49
|
-
if waiters and (active_waiters := [w for w in waiters if not w.call_is_done()]):
|
50
|
-
if parent_call and (
|
51
|
-
matching_waiter := next(
|
52
|
-
(w for w in active_waiters if w._call == parent_call), None
|
53
|
-
)
|
54
|
-
): # if exists an active waiter responsible for the parent call, return it
|
55
|
-
return matching_waiter
|
56
|
-
else: # otherwise, return the most recently created waiter
|
57
|
-
return active_waiters[-1]
|
58
|
-
|
59
|
-
|
60
32
|
def add_waiter_for_thread(waiter: "Waiter", thread: threading.Thread):
|
61
33
|
"""
|
62
34
|
Add a waiter for a thread.
|
@@ -1,46 +1 @@
|
|
1
|
-
### A convenience module to allow for easy switching between pydantic versions.
|
2
1
|
|
3
|
-
### Note this introduces a marginally worse import time, since
|
4
|
-
### the import of any one of these symbols will import all of them.
|
5
|
-
|
6
|
-
### This is a tradeoff we're willing to make for now until pydantic v1 is
|
7
|
-
### no longer supported.
|
8
|
-
|
9
|
-
|
10
|
-
from ._flags import HAS_PYDANTIC_V2
|
11
|
-
|
12
|
-
from ._compat import (
|
13
|
-
model_dump,
|
14
|
-
model_json_schema,
|
15
|
-
model_validate,
|
16
|
-
model_dump_json,
|
17
|
-
model_copy,
|
18
|
-
model_validate_json,
|
19
|
-
TypeAdapter,
|
20
|
-
validate_python,
|
21
|
-
BaseModel,
|
22
|
-
Field,
|
23
|
-
FieldInfo,
|
24
|
-
field_validator,
|
25
|
-
model_validator,
|
26
|
-
)
|
27
|
-
|
28
|
-
from ._types import IncEx
|
29
|
-
|
30
|
-
__all__ = [
|
31
|
-
"model_dump",
|
32
|
-
"model_json_schema",
|
33
|
-
"model_validate",
|
34
|
-
"IncEx",
|
35
|
-
"model_dump_json",
|
36
|
-
"model_copy",
|
37
|
-
"model_validate_json",
|
38
|
-
"TypeAdapter",
|
39
|
-
"validate_python",
|
40
|
-
"BaseModel",
|
41
|
-
"HAS_PYDANTIC_V2",
|
42
|
-
"Field",
|
43
|
-
"FieldInfo",
|
44
|
-
"field_validator",
|
45
|
-
"model_validator",
|
46
|
-
]
|
@@ -1,37 +1,36 @@
|
|
1
1
|
import inspect
|
2
2
|
import typing
|
3
|
+
import warnings
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
if HAS_PYDANTIC_V2:
|
7
|
-
from pydantic.v1 import BaseModel as V1BaseModel
|
8
|
-
else:
|
9
|
-
from pydantic import BaseModel as V1BaseModel
|
5
|
+
import pydantic
|
6
|
+
from pydantic.v1 import BaseModel as V1BaseModel
|
10
7
|
|
11
8
|
|
12
9
|
def is_v1_model(v) -> bool:
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
with warnings.catch_warnings():
|
11
|
+
warnings.filterwarnings(
|
12
|
+
"ignore", category=pydantic.warnings.PydanticDeprecatedSince20
|
13
|
+
)
|
14
|
+
|
15
|
+
if isinstance(v, V1BaseModel):
|
17
16
|
return True
|
18
|
-
|
19
|
-
|
17
|
+
try:
|
18
|
+
if inspect.isclass(v) and issubclass(v, V1BaseModel):
|
19
|
+
return True
|
20
|
+
except TypeError:
|
21
|
+
pass
|
20
22
|
|
21
|
-
|
23
|
+
return False
|
22
24
|
|
23
25
|
|
24
26
|
def is_v1_type(v) -> bool:
|
25
|
-
if
|
26
|
-
|
27
|
-
return True
|
28
|
-
|
29
|
-
try:
|
30
|
-
return v.__module__.startswith("pydantic.v1.types")
|
31
|
-
except AttributeError:
|
32
|
-
return False
|
27
|
+
if is_v1_model(v):
|
28
|
+
return True
|
33
29
|
|
34
|
-
|
30
|
+
try:
|
31
|
+
return v.__module__.startswith("pydantic.v1.types")
|
32
|
+
except AttributeError:
|
33
|
+
return False
|
35
34
|
|
36
35
|
|
37
36
|
def has_v1_type_as_param(signature: inspect.Signature) -> bool:
|
@@ -1,16 +1,15 @@
|
|
1
1
|
"""
|
2
|
-
This module contains an implementation of pydantic v1's ValidateFunction
|
2
|
+
This module contains an implementation of pydantic v1's ValidateFunction
|
3
3
|
modified to validate function arguments and return a pydantic v2 model.
|
4
4
|
|
5
|
-
Specifically it allows for us to validate v2 models used as flow/task
|
5
|
+
Specifically it allows for us to validate v2 models used as flow/task
|
6
6
|
arguments.
|
7
7
|
"""
|
8
8
|
|
9
9
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union
|
10
10
|
|
11
11
|
# importing directly from v2 to be able to create a v2 model
|
12
|
-
from pydantic import BaseModel, create_model
|
13
|
-
from pydantic.v1 import validator
|
12
|
+
from pydantic import BaseModel, ConfigDict, create_model, field_validator
|
14
13
|
from pydantic.v1.decorator import ValidatedFunction
|
15
14
|
from pydantic.v1.errors import ConfigError
|
16
15
|
from pydantic.v1.utils import to_camel
|
@@ -28,28 +27,25 @@ class V2ValidatedFunction(ValidatedFunction):
|
|
28
27
|
fields: Dict[str, Any],
|
29
28
|
takes_args: bool,
|
30
29
|
takes_kwargs: bool,
|
31
|
-
config:
|
30
|
+
config: ConfigDict,
|
32
31
|
) -> None:
|
33
32
|
pos_args = len(self.arg_mapping)
|
34
33
|
|
35
|
-
|
36
|
-
pass
|
37
|
-
|
38
|
-
if not TYPE_CHECKING: # pragma: no branch
|
39
|
-
if isinstance(config, dict):
|
40
|
-
CustomConfig = type("Config", (), config) # noqa: F811
|
41
|
-
elif config is not None:
|
42
|
-
CustomConfig = config # noqa: F811
|
43
|
-
|
44
|
-
if hasattr(CustomConfig, "fields") or hasattr(CustomConfig, "alias_generator"):
|
34
|
+
if config.get("fields") or config.get("alias_generator"):
|
45
35
|
raise ConfigError(
|
46
36
|
'Setting the "fields" and "alias_generator" property on custom Config'
|
47
37
|
" for @validate_arguments is not yet supported, please remove."
|
48
38
|
)
|
49
39
|
|
40
|
+
if "extra" not in config:
|
41
|
+
config["extra"] = "forbid"
|
42
|
+
|
50
43
|
# This is the key change -- inheriting the BaseModel class from v2
|
51
44
|
class DecoratorBaseModel(BaseModel):
|
52
|
-
|
45
|
+
model_config = config
|
46
|
+
|
47
|
+
@field_validator(self.v_args_name, check_fields=False)
|
48
|
+
@classmethod
|
53
49
|
def check_args(cls, v: Optional[List[Any]]) -> Optional[List[Any]]:
|
54
50
|
if takes_args or v is None:
|
55
51
|
return v
|
@@ -59,7 +55,8 @@ class V2ValidatedFunction(ValidatedFunction):
|
|
59
55
|
f" {pos_args + len(v)} given"
|
60
56
|
)
|
61
57
|
|
62
|
-
@
|
58
|
+
@field_validator(self.v_kwargs_name, check_fields=False)
|
59
|
+
@classmethod
|
63
60
|
def check_kwargs(
|
64
61
|
cls, v: Optional[Dict[str, Any]]
|
65
62
|
) -> Optional[Dict[str, Any]]:
|
@@ -70,7 +67,8 @@ class V2ValidatedFunction(ValidatedFunction):
|
|
70
67
|
keys = ", ".join(map(repr, v.keys()))
|
71
68
|
raise TypeError(f"unexpected keyword argument{plural}: {keys}")
|
72
69
|
|
73
|
-
@
|
70
|
+
@field_validator(V_POSITIONAL_ONLY_NAME, check_fields=False)
|
71
|
+
@classmethod
|
74
72
|
def check_positional_only(cls, v: Optional[List[str]]) -> None:
|
75
73
|
if v is None:
|
76
74
|
return
|
@@ -82,7 +80,8 @@ class V2ValidatedFunction(ValidatedFunction):
|
|
82
80
|
f" argument{plural}: {keys}"
|
83
81
|
)
|
84
82
|
|
85
|
-
@
|
83
|
+
@field_validator(V_DUPLICATE_KWARGS, check_fields=False)
|
84
|
+
@classmethod
|
86
85
|
def check_duplicate_kwargs(cls, v: Optional[List[str]]) -> None:
|
87
86
|
if v is None:
|
88
87
|
return
|
@@ -91,10 +90,6 @@ class V2ValidatedFunction(ValidatedFunction):
|
|
91
90
|
keys = ", ".join(map(repr, v))
|
92
91
|
raise TypeError(f"multiple values for argument{plural}: {keys}")
|
93
92
|
|
94
|
-
class Config(CustomConfig):
|
95
|
-
# extra = getattr(CustomConfig, "extra", Extra.forbid)
|
96
|
-
extra = getattr(CustomConfig, "extra", "forbid")
|
97
|
-
|
98
93
|
self.model = create_model(
|
99
94
|
to_camel(self.raw_function.__name__),
|
100
95
|
__base__=DecoratorBaseModel,
|
prefect/_internal/pytz.py
CHANGED
@@ -0,0 +1,61 @@
|
|
1
|
+
import asyncio
|
2
|
+
from functools import wraps
|
3
|
+
from typing import Any, Callable, Tuple, Type
|
4
|
+
|
5
|
+
from prefect.logging.loggers import get_logger
|
6
|
+
from prefect.utilities.math import clamped_poisson_interval
|
7
|
+
|
8
|
+
logger = get_logger("retries")
|
9
|
+
|
10
|
+
|
11
|
+
def exponential_backoff_with_jitter(
|
12
|
+
attempt: int, base_delay: float, max_delay: float
|
13
|
+
) -> float:
|
14
|
+
average_interval = min(base_delay * (2**attempt), max_delay)
|
15
|
+
return clamped_poisson_interval(average_interval, clamping_factor=0.3)
|
16
|
+
|
17
|
+
|
18
|
+
def retry_async_fn(
|
19
|
+
max_attempts: int = 3,
|
20
|
+
backoff_strategy: Callable[
|
21
|
+
[int, float, float], float
|
22
|
+
] = exponential_backoff_with_jitter,
|
23
|
+
base_delay: float = 1,
|
24
|
+
max_delay: float = 10,
|
25
|
+
retry_on_exceptions: Tuple[Type[Exception], ...] = (Exception,),
|
26
|
+
):
|
27
|
+
"""A decorator for retrying an async function.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
max_attempts: The maximum number of times to retry the function.
|
31
|
+
backoff_strategy: A function that takes in the number of attempts, the base
|
32
|
+
delay, and the maximum delay, and returns the delay to use for the next
|
33
|
+
attempt. Defaults to an exponential backoff with jitter.
|
34
|
+
base_delay: The base delay to use for the first attempt.
|
35
|
+
max_delay: The maximum delay to use for the last attempt.
|
36
|
+
retry_on_exceptions: A tuple of exception types to retry on. Defaults to
|
37
|
+
retrying on all exceptions.
|
38
|
+
"""
|
39
|
+
|
40
|
+
def decorator(func):
|
41
|
+
@wraps(func)
|
42
|
+
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
43
|
+
for attempt in range(max_attempts):
|
44
|
+
try:
|
45
|
+
return await func(*args, **kwargs)
|
46
|
+
except retry_on_exceptions as e:
|
47
|
+
if attempt == max_attempts - 1:
|
48
|
+
logger.exception(
|
49
|
+
f"Function {func.__name__!r} failed after {max_attempts} attempts"
|
50
|
+
)
|
51
|
+
raise
|
52
|
+
delay = backoff_strategy(attempt, base_delay, max_delay)
|
53
|
+
logger.warning(
|
54
|
+
f"Attempt {attempt + 1} of function {func.__name__!r} failed with {type(e).__name__}. "
|
55
|
+
f"Retrying in {delay:.2f} seconds..."
|
56
|
+
)
|
57
|
+
await asyncio.sleep(delay)
|
58
|
+
|
59
|
+
return wrapper
|
60
|
+
|
61
|
+
return decorator
|