prefect-client 3.1.0__py3-none-any.whl → 3.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prefect/_internal/compatibility/async_dispatch.py +73 -0
- prefect/_version.py +3 -3
- prefect/client/orchestration.py +11 -9
- prefect/client/schemas/objects.py +21 -0
- prefect/logging/handlers.py +41 -0
- prefect/logging/loggers.py +33 -0
- prefect/logging/logging.yml +8 -0
- prefect/main.py +5 -0
- prefect/settings/models/api.py +1 -1
- prefect/settings/models/experiments.py +5 -0
- prefect/settings/models/logging.py +10 -3
- prefect/settings/models/root.py +1 -1
- prefect/settings/models/server/events.py +1 -1
- prefect/settings/sources.py +21 -0
- prefect/telemetry/__init__.py +0 -0
- prefect/telemetry/bootstrap.py +32 -0
- prefect/telemetry/instrumentation.py +125 -0
- prefect/telemetry/logging.py +26 -0
- prefect/telemetry/processors.py +57 -0
- prefect/types/__init__.py +1 -2
- prefect/workers/base.py +112 -41
- {prefect_client-3.1.0.dist-info → prefect_client-3.1.1.dist-info}/METADATA +1 -1
- {prefect_client-3.1.0.dist-info → prefect_client-3.1.1.dist-info}/RECORD +26 -20
- {prefect_client-3.1.0.dist-info → prefect_client-3.1.1.dist-info}/LICENSE +0 -0
- {prefect_client-3.1.0.dist-info → prefect_client-3.1.1.dist-info}/WHEEL +0 -0
- {prefect_client-3.1.0.dist-info → prefect_client-3.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
import asyncio
|
2
|
+
import inspect
|
3
|
+
from functools import wraps
|
4
|
+
from typing import Any, Callable, Coroutine, Protocol, TypeVar, Union
|
5
|
+
|
6
|
+
from typing_extensions import ParamSpec
|
7
|
+
|
8
|
+
R = TypeVar("R")
|
9
|
+
P = ParamSpec("P")
|
10
|
+
|
11
|
+
|
12
|
+
class AsyncDispatchable(Protocol[P, R]):
|
13
|
+
"""Protocol for functions decorated with async_dispatch."""
|
14
|
+
|
15
|
+
def __call__(
|
16
|
+
self, *args: P.args, **kwargs: P.kwargs
|
17
|
+
) -> Union[R, Coroutine[Any, Any, R]]:
|
18
|
+
...
|
19
|
+
|
20
|
+
aio: Callable[P, Coroutine[Any, Any, R]]
|
21
|
+
sync: Callable[P, R]
|
22
|
+
|
23
|
+
|
24
|
+
def is_in_async_context() -> bool:
|
25
|
+
"""Check if we're in an async context."""
|
26
|
+
try:
|
27
|
+
# First check if we're in a coroutine
|
28
|
+
if asyncio.current_task() is not None:
|
29
|
+
return True
|
30
|
+
|
31
|
+
# Check if we have a loop and it's running
|
32
|
+
loop = asyncio.get_event_loop()
|
33
|
+
return loop.is_running()
|
34
|
+
except RuntimeError:
|
35
|
+
return False
|
36
|
+
|
37
|
+
|
38
|
+
def async_dispatch(
|
39
|
+
async_impl: Callable[P, Coroutine[Any, Any, R]],
|
40
|
+
) -> Callable[[Callable[P, R]], AsyncDispatchable[P, R]]:
|
41
|
+
"""
|
42
|
+
Decorator that adds async compatibility to a sync function.
|
43
|
+
|
44
|
+
The decorated function will:
|
45
|
+
- Return a coroutine when in an async context (detected via running event loop)
|
46
|
+
- Run synchronously when in a sync context
|
47
|
+
- Provide .aio for explicit async access
|
48
|
+
- Provide .sync for explicit sync access
|
49
|
+
|
50
|
+
Args:
|
51
|
+
async_impl: The async implementation to dispatch to when async execution
|
52
|
+
is needed
|
53
|
+
"""
|
54
|
+
if not inspect.iscoroutinefunction(async_impl):
|
55
|
+
raise TypeError(
|
56
|
+
"async_impl must be an async function to dispatch in async contexts"
|
57
|
+
)
|
58
|
+
|
59
|
+
def decorator(sync_fn: Callable[P, R]) -> AsyncDispatchable[P, R]:
|
60
|
+
@wraps(sync_fn)
|
61
|
+
def wrapper(
|
62
|
+
*args: P.args, **kwargs: P.kwargs
|
63
|
+
) -> Union[R, Coroutine[Any, Any, R]]:
|
64
|
+
if is_in_async_context():
|
65
|
+
return async_impl(*args, **kwargs)
|
66
|
+
return sync_fn(*args, **kwargs)
|
67
|
+
|
68
|
+
# Attach both async and sync implementations directly
|
69
|
+
wrapper.aio = async_impl
|
70
|
+
wrapper.sync = sync_fn
|
71
|
+
return wrapper # type: ignore
|
72
|
+
|
73
|
+
return decorator
|
prefect/_version.py
CHANGED
@@ -8,11 +8,11 @@ import json
|
|
8
8
|
|
9
9
|
version_json = '''
|
10
10
|
{
|
11
|
-
"date": "2024-
|
11
|
+
"date": "2024-11-08T12:38:16-0800",
|
12
12
|
"dirty": true,
|
13
13
|
"error": null,
|
14
|
-
"full-revisionid": "
|
15
|
-
"version": "3.1.
|
14
|
+
"full-revisionid": "6b50a2b9f9d4ebf59703c55e1156c6f79151f1c3",
|
15
|
+
"version": "3.1.1"
|
16
16
|
}
|
17
17
|
''' # END VERSION_JSON
|
18
18
|
|
prefect/client/orchestration.py
CHANGED
@@ -99,6 +99,7 @@ from prefect.client.schemas.objects import (
|
|
99
99
|
TaskRunResult,
|
100
100
|
Variable,
|
101
101
|
Worker,
|
102
|
+
WorkerMetadata,
|
102
103
|
WorkPool,
|
103
104
|
WorkQueue,
|
104
105
|
WorkQueueStatusDetail,
|
@@ -2596,6 +2597,7 @@ class PrefectClient:
|
|
2596
2597
|
worker_name: str,
|
2597
2598
|
heartbeat_interval_seconds: Optional[float] = None,
|
2598
2599
|
get_worker_id: bool = False,
|
2600
|
+
worker_metadata: Optional[WorkerMetadata] = None,
|
2599
2601
|
) -> Optional[UUID]:
|
2600
2602
|
"""
|
2601
2603
|
Sends a worker heartbeat for a given work pool.
|
@@ -2604,20 +2606,20 @@ class PrefectClient:
|
|
2604
2606
|
work_pool_name: The name of the work pool to heartbeat against.
|
2605
2607
|
worker_name: The name of the worker sending the heartbeat.
|
2606
2608
|
return_id: Whether to return the worker ID. Note: will return `None` if the connected server does not support returning worker IDs, even if `return_id` is `True`.
|
2609
|
+
worker_metadata: Metadata about the worker to send to the server.
|
2607
2610
|
"""
|
2608
|
-
|
2611
|
+
params = {
|
2612
|
+
"name": worker_name,
|
2613
|
+
"heartbeat_interval_seconds": heartbeat_interval_seconds,
|
2614
|
+
}
|
2615
|
+
if worker_metadata:
|
2616
|
+
params["worker_metadata"] = worker_metadata.model_dump(mode="json")
|
2609
2617
|
if get_worker_id:
|
2610
|
-
|
2611
|
-
else:
|
2612
|
-
return_dict = {}
|
2618
|
+
params["return_id"] = get_worker_id
|
2613
2619
|
|
2614
2620
|
resp = await self._client.post(
|
2615
2621
|
f"/work_pools/{work_pool_name}/workers/heartbeat",
|
2616
|
-
json=
|
2617
|
-
"name": worker_name,
|
2618
|
-
"heartbeat_interval_seconds": heartbeat_interval_seconds,
|
2619
|
-
}
|
2620
|
-
| return_dict,
|
2622
|
+
json=params,
|
2621
2623
|
)
|
2622
2624
|
|
2623
2625
|
if (
|
@@ -1689,3 +1689,24 @@ class CsrfToken(ObjectBaseModel):
|
|
1689
1689
|
|
1690
1690
|
|
1691
1691
|
__getattr__ = getattr_migration(__name__)
|
1692
|
+
|
1693
|
+
|
1694
|
+
class Integration(PrefectBaseModel):
|
1695
|
+
"""A representation of an installed Prefect integration."""
|
1696
|
+
|
1697
|
+
name: str = Field(description="The name of the Prefect integration.")
|
1698
|
+
version: str = Field(description="The version of the Prefect integration.")
|
1699
|
+
|
1700
|
+
|
1701
|
+
class WorkerMetadata(PrefectBaseModel):
|
1702
|
+
"""
|
1703
|
+
Worker metadata.
|
1704
|
+
|
1705
|
+
We depend on the structure of `integrations`, but otherwise, worker classes
|
1706
|
+
should support flexible metadata.
|
1707
|
+
"""
|
1708
|
+
|
1709
|
+
integrations: List[Integration] = Field(
|
1710
|
+
default=..., description="Prefect integrations installed in the worker."
|
1711
|
+
)
|
1712
|
+
model_config = ConfigDict(extra="allow")
|
prefect/logging/handlers.py
CHANGED
@@ -32,6 +32,7 @@ from prefect.settings import (
|
|
32
32
|
PREFECT_LOGGING_TO_API_BATCH_SIZE,
|
33
33
|
PREFECT_LOGGING_TO_API_MAX_LOG_SIZE,
|
34
34
|
PREFECT_LOGGING_TO_API_WHEN_MISSING_FLOW,
|
35
|
+
get_current_settings,
|
35
36
|
)
|
36
37
|
|
37
38
|
|
@@ -180,6 +181,7 @@ class APILogHandler(logging.Handler):
|
|
180
181
|
"""
|
181
182
|
flow_run_id = getattr(record, "flow_run_id", None)
|
182
183
|
task_run_id = getattr(record, "task_run_id", None)
|
184
|
+
worker_id = getattr(record, "worker_id", None)
|
183
185
|
|
184
186
|
if not flow_run_id:
|
185
187
|
try:
|
@@ -215,6 +217,7 @@ class APILogHandler(logging.Handler):
|
|
215
217
|
log = LogCreate(
|
216
218
|
flow_run_id=flow_run_id if is_uuid_like else None,
|
217
219
|
task_run_id=task_run_id,
|
220
|
+
worker_id=worker_id,
|
218
221
|
name=record.name,
|
219
222
|
level=record.levelno,
|
220
223
|
timestamp=pendulum.from_timestamp(
|
@@ -236,6 +239,44 @@ class APILogHandler(logging.Handler):
|
|
236
239
|
return len(json.dumps(log).encode())
|
237
240
|
|
238
241
|
|
242
|
+
class WorkerAPILogHandler(APILogHandler):
|
243
|
+
def emit(self, record: logging.LogRecord):
|
244
|
+
if get_current_settings().experiments.worker_logging_to_api_enabled:
|
245
|
+
super().emit(record)
|
246
|
+
else:
|
247
|
+
return
|
248
|
+
|
249
|
+
def prepare(self, record: logging.LogRecord) -> Dict[str, Any]:
|
250
|
+
"""
|
251
|
+
Convert a `logging.LogRecord` to the API `LogCreate` schema and serialize.
|
252
|
+
|
253
|
+
This will add in the worker id to the log.
|
254
|
+
|
255
|
+
Logs exceeding the maximum size will be dropped.
|
256
|
+
"""
|
257
|
+
|
258
|
+
worker_id = getattr(record, "worker_id", None)
|
259
|
+
|
260
|
+
log = LogCreate(
|
261
|
+
worker_id=worker_id,
|
262
|
+
name=record.name,
|
263
|
+
level=record.levelno,
|
264
|
+
timestamp=pendulum.from_timestamp(
|
265
|
+
getattr(record, "created", None) or time.time()
|
266
|
+
),
|
267
|
+
message=self.format(record),
|
268
|
+
).model_dump(mode="json")
|
269
|
+
|
270
|
+
log_size = log["__payload_size__"] = self._get_payload_size(log)
|
271
|
+
if log_size > PREFECT_LOGGING_TO_API_MAX_LOG_SIZE.value():
|
272
|
+
raise ValueError(
|
273
|
+
f"Log of size {log_size} is greater than the max size of "
|
274
|
+
f"{PREFECT_LOGGING_TO_API_MAX_LOG_SIZE.value()}"
|
275
|
+
)
|
276
|
+
|
277
|
+
return log
|
278
|
+
|
279
|
+
|
239
280
|
class PrefectConsoleHandler(logging.StreamHandler):
|
240
281
|
def __init__(
|
241
282
|
self,
|
prefect/logging/loggers.py
CHANGED
@@ -12,6 +12,7 @@ from typing_extensions import Self
|
|
12
12
|
import prefect
|
13
13
|
from prefect.exceptions import MissingContextError
|
14
14
|
from prefect.logging.filters import ObfuscateApiKeyFilter
|
15
|
+
from prefect.telemetry.logging import add_telemetry_log_handler
|
15
16
|
|
16
17
|
if TYPE_CHECKING:
|
17
18
|
from prefect.client.schemas import FlowRun as ClientFlowRun
|
@@ -19,6 +20,7 @@ if TYPE_CHECKING:
|
|
19
20
|
from prefect.context import RunContext
|
20
21
|
from prefect.flows import Flow
|
21
22
|
from prefect.tasks import Task
|
23
|
+
from prefect.workers.base import BaseWorker
|
22
24
|
|
23
25
|
|
24
26
|
class PrefectLogAdapter(logging.LoggerAdapter):
|
@@ -75,6 +77,8 @@ def get_logger(name: Optional[str] = None) -> logging.Logger:
|
|
75
77
|
obfuscate_api_key_filter = ObfuscateApiKeyFilter()
|
76
78
|
logger.addFilter(obfuscate_api_key_filter)
|
77
79
|
|
80
|
+
add_telemetry_log_handler(logger=logger)
|
81
|
+
|
78
82
|
return logger
|
79
83
|
|
80
84
|
|
@@ -136,6 +140,12 @@ def get_run_logger(
|
|
136
140
|
else:
|
137
141
|
raise MissingContextError("There is no active flow or task run context.")
|
138
142
|
|
143
|
+
if isinstance(logger, logging.LoggerAdapter):
|
144
|
+
assert isinstance(logger.logger, logging.Logger)
|
145
|
+
add_telemetry_log_handler(logger.logger)
|
146
|
+
else:
|
147
|
+
add_telemetry_log_handler(logger)
|
148
|
+
|
139
149
|
return logger
|
140
150
|
|
141
151
|
|
@@ -205,6 +215,29 @@ def task_run_logger(
|
|
205
215
|
)
|
206
216
|
|
207
217
|
|
218
|
+
def get_worker_logger(worker: "BaseWorker", name: Optional[str] = None):
|
219
|
+
"""
|
220
|
+
Create a worker logger with the worker's metadata attached.
|
221
|
+
|
222
|
+
If the worker has a backend_id, it will be attached to the log records.
|
223
|
+
If the worker does not have a backend_id a basic logger will be returned.
|
224
|
+
If the worker does not have a backend_id attribute, a basic logger will be returned.
|
225
|
+
"""
|
226
|
+
|
227
|
+
worker_log_name = name or f"workers.{worker.__class__.type}.{worker.name.lower()}"
|
228
|
+
|
229
|
+
worker_id = getattr(worker, "backend_id", None)
|
230
|
+
if worker_id:
|
231
|
+
return PrefectLogAdapter(
|
232
|
+
get_logger(worker_log_name),
|
233
|
+
extra={
|
234
|
+
"worker_id": str(worker.backend_id),
|
235
|
+
},
|
236
|
+
)
|
237
|
+
else:
|
238
|
+
return get_logger(worker_log_name)
|
239
|
+
|
240
|
+
|
208
241
|
@contextmanager
|
209
242
|
def disable_logger(name: str):
|
210
243
|
"""
|
prefect/logging/logging.yml
CHANGED
@@ -69,6 +69,10 @@ handlers:
|
|
69
69
|
class: logging.StreamHandler
|
70
70
|
formatter: debug
|
71
71
|
|
72
|
+
worker_api:
|
73
|
+
level: 0
|
74
|
+
class: prefect.logging.handlers.WorkerAPILogHandler
|
75
|
+
|
72
76
|
loggers:
|
73
77
|
prefect:
|
74
78
|
level: "${PREFECT_LOGGING_LEVEL}"
|
@@ -86,6 +90,10 @@ loggers:
|
|
86
90
|
level: NOTSET
|
87
91
|
handlers: [api]
|
88
92
|
|
93
|
+
prefect.workers:
|
94
|
+
level: NOTSET
|
95
|
+
handlers: [worker_api]
|
96
|
+
|
89
97
|
prefect.server:
|
90
98
|
level: "${PREFECT_SERVER_LOGGING_LEVEL}"
|
91
99
|
|
prefect/main.py
CHANGED
@@ -57,6 +57,11 @@ prefect.logging.get_logger("profiles").debug(
|
|
57
57
|
f"Using profile {prefect.context.get_settings_context().profile.name!r}"
|
58
58
|
)
|
59
59
|
|
60
|
+
# Configure telemetry
|
61
|
+
import prefect.telemetry.bootstrap
|
62
|
+
|
63
|
+
prefect.telemetry.bootstrap.setup_telemetry()
|
64
|
+
|
60
65
|
|
61
66
|
from prefect._internal.compatibility.deprecated import (
|
62
67
|
inject_renamed_module_alias_finder,
|
prefect/settings/models/api.py
CHANGED
@@ -25,7 +25,7 @@ class APISettings(PrefectBaseSettings):
|
|
25
25
|
)
|
26
26
|
tls_insecure_skip_verify: bool = Field(
|
27
27
|
default=False,
|
28
|
-
description="If `True`, disables SSL checking to allow insecure requests.
|
28
|
+
description="If `True`, disables SSL checking to allow insecure requests. Setting to False is recommended only during development. For example, when using self-signed certificates.",
|
29
29
|
)
|
30
30
|
ssl_cert_file: Optional[str] = Field(
|
31
31
|
default=os.environ.get("SSL_CERT_FILE"),
|
@@ -22,3 +22,8 @@ class ExperimentsSettings(PrefectBaseSettings):
|
|
22
22
|
default=False,
|
23
23
|
description="Enables the logging of worker logs to Prefect Cloud.",
|
24
24
|
)
|
25
|
+
|
26
|
+
telemetry_enabled: bool = Field(
|
27
|
+
default=False,
|
28
|
+
description="Enables sending telemetry to Prefect Cloud.",
|
29
|
+
)
|
@@ -1,11 +1,18 @@
|
|
1
|
+
from functools import partial
|
1
2
|
from pathlib import Path
|
2
3
|
from typing import Annotated, Literal, Optional, Union
|
3
4
|
|
4
|
-
from pydantic import
|
5
|
+
from pydantic import (
|
6
|
+
AliasChoices,
|
7
|
+
AliasPath,
|
8
|
+
BeforeValidator,
|
9
|
+
Field,
|
10
|
+
model_validator,
|
11
|
+
)
|
5
12
|
from typing_extensions import Self
|
6
13
|
|
7
14
|
from prefect.settings.base import PrefectBaseSettings, _build_settings_config
|
8
|
-
from prefect.types import LogLevel
|
15
|
+
from prefect.types import LogLevel, validate_set_T_from_delim_string
|
9
16
|
|
10
17
|
|
11
18
|
def max_log_size_smaller_than_batch_size(values):
|
@@ -97,7 +104,7 @@ class LoggingSettings(PrefectBaseSettings):
|
|
97
104
|
|
98
105
|
extra_loggers: Annotated[
|
99
106
|
Union[str, list[str], None],
|
100
|
-
|
107
|
+
BeforeValidator(partial(validate_set_T_from_delim_string, type_=str)),
|
101
108
|
] = Field(
|
102
109
|
default=None,
|
103
110
|
description="Additional loggers to attach to Prefect logging at runtime.",
|
prefect/settings/models/root.py
CHANGED
@@ -72,7 +72,7 @@ class Settings(PrefectBaseSettings):
|
|
72
72
|
|
73
73
|
client: ClientSettings = Field(
|
74
74
|
default_factory=ClientSettings,
|
75
|
-
description="Settings for
|
75
|
+
description="Settings for controlling API client behavior",
|
76
76
|
)
|
77
77
|
|
78
78
|
cloud: CloudSettings = Field(
|
@@ -128,7 +128,7 @@ class ServerEventsSettings(PrefectBaseSettings):
|
|
128
128
|
|
129
129
|
messaging_cache: str = Field(
|
130
130
|
default="prefect.server.utilities.messaging.memory",
|
131
|
-
description="Which cache implementation to use for the events system.
|
131
|
+
description="Which cache implementation to use for the events system. Should point to a module that exports a Cache class.",
|
132
132
|
validation_alias=AliasChoices(
|
133
133
|
AliasPath("messaging_cache"),
|
134
134
|
"prefect_server_events_messaging_cache",
|
prefect/settings/sources.py
CHANGED
@@ -4,6 +4,7 @@ import warnings
|
|
4
4
|
from pathlib import Path
|
5
5
|
from typing import Any, Dict, List, Optional, Tuple, Type
|
6
6
|
|
7
|
+
import dotenv
|
7
8
|
import toml
|
8
9
|
from pydantic import AliasChoices
|
9
10
|
from pydantic.fields import FieldInfo
|
@@ -15,6 +16,7 @@ from pydantic_settings import (
|
|
15
16
|
from pydantic_settings.sources import ConfigFileSourceMixin
|
16
17
|
|
17
18
|
from prefect.settings.constants import DEFAULT_PREFECT_HOME, DEFAULT_PROFILES_PATH
|
19
|
+
from prefect.utilities.collections import get_from_dict
|
18
20
|
|
19
21
|
|
20
22
|
class EnvFilterSettingsSource(EnvSettingsSource):
|
@@ -230,6 +232,25 @@ def _get_profiles_path() -> Path:
|
|
230
232
|
return DEFAULT_PROFILES_PATH
|
231
233
|
if env_path := os.getenv("PREFECT_PROFILES_PATH"):
|
232
234
|
return Path(env_path)
|
235
|
+
if dotenv_path := dotenv.dotenv_values(".env").get("PREFECT_PROFILES_PATH"):
|
236
|
+
return Path(dotenv_path)
|
237
|
+
if toml_path := _get_profiles_path_from_toml("prefect.toml", ["profiles_path"]):
|
238
|
+
return Path(toml_path)
|
239
|
+
if pyproject_path := _get_profiles_path_from_toml(
|
240
|
+
"pyproject.toml", ["tool", "prefect", "profiles_path"]
|
241
|
+
):
|
242
|
+
return Path(pyproject_path)
|
233
243
|
if not (DEFAULT_PREFECT_HOME / "profiles.toml").exists():
|
234
244
|
return DEFAULT_PROFILES_PATH
|
235
245
|
return DEFAULT_PREFECT_HOME / "profiles.toml"
|
246
|
+
|
247
|
+
|
248
|
+
def _get_profiles_path_from_toml(path: str, keys: List[str]) -> Optional[str]:
|
249
|
+
"""Helper to get the profiles path from a toml file."""
|
250
|
+
|
251
|
+
try:
|
252
|
+
toml_data = toml.load(path)
|
253
|
+
except FileNotFoundError:
|
254
|
+
return None
|
255
|
+
|
256
|
+
return get_from_dict(toml_data, keys)
|
File without changes
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from typing import TYPE_CHECKING, Union
|
2
|
+
|
3
|
+
import prefect.settings
|
4
|
+
from prefect.client.base import ServerType, determine_server_type
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from opentelemetry.sdk._logs import LoggerProvider
|
8
|
+
from opentelemetry.sdk.metrics import MeterProvider
|
9
|
+
from opentelemetry.sdk.trace import TracerProvider
|
10
|
+
|
11
|
+
|
12
|
+
def setup_telemetry() -> (
|
13
|
+
Union[
|
14
|
+
tuple["TracerProvider", "MeterProvider", "LoggerProvider"],
|
15
|
+
tuple[None, None, None],
|
16
|
+
]
|
17
|
+
):
|
18
|
+
settings = prefect.settings.get_current_settings()
|
19
|
+
if not settings.experiments.telemetry_enabled:
|
20
|
+
return None, None, None
|
21
|
+
|
22
|
+
server_type = determine_server_type()
|
23
|
+
if server_type != ServerType.CLOUD:
|
24
|
+
return None, None, None
|
25
|
+
|
26
|
+
assert settings.api.key
|
27
|
+
assert settings.api.url
|
28
|
+
|
29
|
+
# This import is here to defer importing of the `opentelemetry` packages.
|
30
|
+
from .instrumentation import setup_exporters
|
31
|
+
|
32
|
+
return setup_exporters(settings.api.url, settings.api.key.get_secret_value())
|
@@ -0,0 +1,125 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import re
|
4
|
+
from typing import TYPE_CHECKING
|
5
|
+
from urllib.parse import urljoin
|
6
|
+
from uuid import UUID
|
7
|
+
|
8
|
+
from opentelemetry import metrics, trace
|
9
|
+
from opentelemetry._logs import set_logger_provider
|
10
|
+
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
|
11
|
+
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
|
12
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
13
|
+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
14
|
+
from opentelemetry.sdk._logs.export import SimpleLogRecordProcessor
|
15
|
+
from opentelemetry.sdk.metrics import MeterProvider
|
16
|
+
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
|
17
|
+
from opentelemetry.sdk.resources import Resource
|
18
|
+
from opentelemetry.sdk.trace import TracerProvider
|
19
|
+
|
20
|
+
from .logging import set_log_handler
|
21
|
+
from .processors import InFlightSpanProcessor
|
22
|
+
|
23
|
+
if TYPE_CHECKING:
|
24
|
+
from opentelemetry.sdk._logs import LoggerProvider
|
25
|
+
|
26
|
+
UUID_REGEX = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
27
|
+
|
28
|
+
ACCOUNTS_PREFIX = "accounts/"
|
29
|
+
ACCOUNT_ID_REGEX = f"{ACCOUNTS_PREFIX}{UUID_REGEX}"
|
30
|
+
|
31
|
+
WORKSPACES_PREFIX = "workspaces/"
|
32
|
+
WORKSPACE_ID_REGEX = f"{WORKSPACES_PREFIX}{UUID_REGEX}"
|
33
|
+
|
34
|
+
|
35
|
+
def extract_account_and_workspace_id(url: str) -> tuple[UUID, UUID]:
|
36
|
+
account_id, workspace_id = None, None
|
37
|
+
|
38
|
+
if res := re.search(ACCOUNT_ID_REGEX, url):
|
39
|
+
account_id = UUID(res.group().removeprefix(ACCOUNTS_PREFIX))
|
40
|
+
|
41
|
+
if res := re.search(WORKSPACE_ID_REGEX, url):
|
42
|
+
workspace_id = UUID(res.group().removeprefix(WORKSPACES_PREFIX))
|
43
|
+
|
44
|
+
if account_id and workspace_id:
|
45
|
+
return account_id, workspace_id
|
46
|
+
|
47
|
+
raise ValueError(
|
48
|
+
f"Could not extract account and workspace id from API url: {url!r}"
|
49
|
+
)
|
50
|
+
|
51
|
+
|
52
|
+
def _url_join(base_url: str, path: str) -> str:
|
53
|
+
return urljoin(base_url.rstrip("/") + "/", path.lstrip("/"))
|
54
|
+
|
55
|
+
|
56
|
+
def setup_exporters(
|
57
|
+
api_url: str, api_key: str
|
58
|
+
) -> tuple[TracerProvider, MeterProvider, "LoggerProvider"]:
|
59
|
+
account_id, workspace_id = extract_account_and_workspace_id(api_url)
|
60
|
+
telemetry_url = _url_join(api_url, "telemetry/")
|
61
|
+
|
62
|
+
headers = {
|
63
|
+
"Authorization": f"Bearer {api_key}",
|
64
|
+
}
|
65
|
+
|
66
|
+
resource = Resource.create(
|
67
|
+
{
|
68
|
+
"service.name": "prefect",
|
69
|
+
"service.instance.id": os.uname().nodename,
|
70
|
+
"prefect.account": str(account_id),
|
71
|
+
"prefect.workspace": str(workspace_id),
|
72
|
+
}
|
73
|
+
)
|
74
|
+
|
75
|
+
trace_provider = _setup_trace_provider(resource, headers, telemetry_url)
|
76
|
+
meter_provider = _setup_meter_provider(resource, headers, telemetry_url)
|
77
|
+
logger_provider = _setup_logger_provider(resource, headers, telemetry_url)
|
78
|
+
|
79
|
+
return trace_provider, meter_provider, logger_provider
|
80
|
+
|
81
|
+
|
82
|
+
def _setup_trace_provider(
|
83
|
+
resource: Resource, headers: dict[str, str], telemetry_url: str
|
84
|
+
) -> TracerProvider:
|
85
|
+
trace_provider = TracerProvider(resource=resource)
|
86
|
+
otlp_span_exporter = OTLPSpanExporter(
|
87
|
+
endpoint=_url_join(telemetry_url, "v1/traces"),
|
88
|
+
headers=headers,
|
89
|
+
)
|
90
|
+
trace_provider.add_span_processor(InFlightSpanProcessor(otlp_span_exporter))
|
91
|
+
trace.set_tracer_provider(trace_provider)
|
92
|
+
|
93
|
+
return trace_provider
|
94
|
+
|
95
|
+
|
96
|
+
def _setup_meter_provider(
|
97
|
+
resource: Resource, headers: dict[str, str], telemetry_url: str
|
98
|
+
) -> MeterProvider:
|
99
|
+
metric_reader = PeriodicExportingMetricReader(
|
100
|
+
OTLPMetricExporter(
|
101
|
+
endpoint=_url_join(telemetry_url, "v1/metrics"),
|
102
|
+
headers=headers,
|
103
|
+
)
|
104
|
+
)
|
105
|
+
meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader])
|
106
|
+
metrics.set_meter_provider(meter_provider)
|
107
|
+
|
108
|
+
return meter_provider
|
109
|
+
|
110
|
+
|
111
|
+
def _setup_logger_provider(
|
112
|
+
resource: Resource, headers: dict[str, str], telemetry_url: str
|
113
|
+
) -> LoggerProvider:
|
114
|
+
logger_provider = LoggerProvider(resource=resource)
|
115
|
+
otlp_exporter = OTLPLogExporter(
|
116
|
+
endpoint=_url_join(telemetry_url, "v1/logs"),
|
117
|
+
headers=headers,
|
118
|
+
)
|
119
|
+
logger_provider.add_log_record_processor(SimpleLogRecordProcessor(otlp_exporter))
|
120
|
+
set_logger_provider(logger_provider)
|
121
|
+
log_handler = LoggingHandler(level=logging.NOTSET, logger_provider=logger_provider)
|
122
|
+
|
123
|
+
set_log_handler(log_handler)
|
124
|
+
|
125
|
+
return logger_provider
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import TYPE_CHECKING, Optional
|
3
|
+
|
4
|
+
if TYPE_CHECKING:
|
5
|
+
from opentelemetry.sdk._logs import LoggingHandler
|
6
|
+
|
7
|
+
_log_handler: Optional["LoggingHandler"] = None
|
8
|
+
|
9
|
+
|
10
|
+
def set_log_handler(log_handler: Optional["LoggingHandler"]) -> None:
|
11
|
+
"""Set the OTLP log handler."""
|
12
|
+
global _log_handler
|
13
|
+
_log_handler = log_handler
|
14
|
+
|
15
|
+
|
16
|
+
def get_log_handler() -> Optional["LoggingHandler"]:
|
17
|
+
"""Get the OTLP log handler."""
|
18
|
+
global _log_handler
|
19
|
+
return _log_handler
|
20
|
+
|
21
|
+
|
22
|
+
def add_telemetry_log_handler(logger: logging.Logger) -> None:
|
23
|
+
"""Add the OTLP log handler to the given logger if the log handler has
|
24
|
+
been configured."""
|
25
|
+
if log_handler := get_log_handler():
|
26
|
+
logger.addHandler(log_handler)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import time
|
2
|
+
from threading import Event, Lock, Thread
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from opentelemetry.context import Context
|
6
|
+
from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
|
7
|
+
from opentelemetry.sdk.trace.export import SpanExporter
|
8
|
+
|
9
|
+
|
10
|
+
class InFlightSpanProcessor(SpanProcessor):
|
11
|
+
def __init__(self, span_exporter: SpanExporter):
|
12
|
+
self.span_exporter = span_exporter
|
13
|
+
self._in_flight = {}
|
14
|
+
self._lock = Lock()
|
15
|
+
self._stop_event = Event()
|
16
|
+
self._export_thread = Thread(target=self._export_periodically, daemon=True)
|
17
|
+
self._export_thread.start()
|
18
|
+
|
19
|
+
def _export_periodically(self) -> None:
|
20
|
+
while not self._stop_event.is_set():
|
21
|
+
time.sleep(1)
|
22
|
+
with self._lock:
|
23
|
+
to_export = [
|
24
|
+
self._readable_span(span) for span in self._in_flight.values()
|
25
|
+
]
|
26
|
+
if to_export:
|
27
|
+
self.span_exporter.export(to_export)
|
28
|
+
|
29
|
+
def _readable_span(self, span: Span) -> ReadableSpan:
|
30
|
+
readable = span._readable_span()
|
31
|
+
readable._end_time = time.time_ns()
|
32
|
+
readable._attributes = {
|
33
|
+
**(readable._attributes or {}),
|
34
|
+
"prefect.in-flight": True,
|
35
|
+
}
|
36
|
+
return readable
|
37
|
+
|
38
|
+
def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None:
|
39
|
+
if not span.context or not span.context.trace_flags.sampled:
|
40
|
+
return
|
41
|
+
with self._lock:
|
42
|
+
self._in_flight[span.context.span_id] = span
|
43
|
+
|
44
|
+
def on_end(self, span: ReadableSpan) -> None:
|
45
|
+
if not span.context or not span.context.trace_flags.sampled:
|
46
|
+
return
|
47
|
+
with self._lock:
|
48
|
+
del self._in_flight[span.context.span_id]
|
49
|
+
self.span_exporter.export((span,))
|
50
|
+
|
51
|
+
def shutdown(self) -> None:
|
52
|
+
self._stop_event.set()
|
53
|
+
self._export_thread.join()
|
54
|
+
self.span_exporter.shutdown()
|
55
|
+
|
56
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
57
|
+
return True
|
prefect/types/__init__.py
CHANGED
@@ -7,7 +7,6 @@ import pydantic
|
|
7
7
|
from pydantic import (
|
8
8
|
BeforeValidator,
|
9
9
|
Field,
|
10
|
-
SecretStr,
|
11
10
|
StrictBool,
|
12
11
|
StrictFloat,
|
13
12
|
StrictInt,
|
@@ -110,7 +109,7 @@ def validate_set_T_from_delim_string(
|
|
110
109
|
T_adapter = TypeAdapter(type_)
|
111
110
|
delim = delim or ","
|
112
111
|
if isinstance(value, str):
|
113
|
-
return {T_adapter.validate_strings(s) for s in value.split(delim)}
|
112
|
+
return {T_adapter.validate_strings(s.strip()) for s in value.split(delim)}
|
114
113
|
errors = []
|
115
114
|
try:
|
116
115
|
return {T_adapter.validate_python(value)}
|
prefect/workers/base.py
CHANGED
@@ -8,7 +8,9 @@ from uuid import UUID, uuid4
|
|
8
8
|
|
9
9
|
import anyio
|
10
10
|
import anyio.abc
|
11
|
+
import httpx
|
11
12
|
import pendulum
|
13
|
+
from importlib_metadata import distributions
|
12
14
|
from pydantic import BaseModel, Field, PrivateAttr, field_validator
|
13
15
|
from pydantic.json_schema import GenerateJsonSchema
|
14
16
|
from typing_extensions import Literal
|
@@ -18,7 +20,12 @@ from prefect._internal.schemas.validators import return_v_or_none
|
|
18
20
|
from prefect.client.base import ServerType
|
19
21
|
from prefect.client.orchestration import PrefectClient, get_client
|
20
22
|
from prefect.client.schemas.actions import WorkPoolCreate, WorkPoolUpdate
|
21
|
-
from prefect.client.schemas.objects import
|
23
|
+
from prefect.client.schemas.objects import (
|
24
|
+
Integration,
|
25
|
+
StateType,
|
26
|
+
WorkerMetadata,
|
27
|
+
WorkPool,
|
28
|
+
)
|
22
29
|
from prefect.client.utilities import inject_client
|
23
30
|
from prefect.events import Event, RelatedResource, emit_event
|
24
31
|
from prefect.events.related import object_as_related_resource, tags_as_related_resources
|
@@ -26,7 +33,11 @@ from prefect.exceptions import (
|
|
26
33
|
Abort,
|
27
34
|
ObjectNotFound,
|
28
35
|
)
|
29
|
-
from prefect.logging.loggers import
|
36
|
+
from prefect.logging.loggers import (
|
37
|
+
PrefectLogAdapter,
|
38
|
+
flow_run_logger,
|
39
|
+
get_worker_logger,
|
40
|
+
)
|
30
41
|
from prefect.plugins import load_prefect_collections
|
31
42
|
from prefect.settings import (
|
32
43
|
PREFECT_API_URL,
|
@@ -50,6 +61,7 @@ from prefect.utilities.templating import (
|
|
50
61
|
resolve_block_document_references,
|
51
62
|
resolve_variables,
|
52
63
|
)
|
64
|
+
from prefect.utilities.urls import url_for
|
53
65
|
|
54
66
|
if TYPE_CHECKING:
|
55
67
|
from prefect.client.schemas.objects import Flow, FlowRun
|
@@ -407,7 +419,8 @@ class BaseWorker(abc.ABC):
|
|
407
419
|
raise ValueError("Worker name cannot contain '/' or '%'")
|
408
420
|
self.name = name or f"{self.__class__.__name__} {uuid4()}"
|
409
421
|
self._started_event: Optional[Event] = None
|
410
|
-
self.
|
422
|
+
self.backend_id: Optional[UUID] = None
|
423
|
+
self._logger = get_worker_logger(self)
|
411
424
|
|
412
425
|
self.is_setup = False
|
413
426
|
self._create_pool_if_not_found = create_pool_if_not_found
|
@@ -422,7 +435,6 @@ class BaseWorker(abc.ABC):
|
|
422
435
|
heartbeat_interval_seconds or PREFECT_WORKER_HEARTBEAT_SECONDS.value()
|
423
436
|
)
|
424
437
|
|
425
|
-
self.backend_id: Optional[UUID] = None
|
426
438
|
self._work_pool: Optional[WorkPool] = None
|
427
439
|
self._exit_stack: AsyncExitStack = AsyncExitStack()
|
428
440
|
self._runs_task_group: Optional[anyio.abc.TaskGroup] = None
|
@@ -433,6 +445,7 @@ class BaseWorker(abc.ABC):
|
|
433
445
|
self._submitting_flow_run_ids = set()
|
434
446
|
self._cancelling_flow_run_ids = set()
|
435
447
|
self._scheduled_task_scopes = set()
|
448
|
+
self._worker_metadata_sent = False
|
436
449
|
|
437
450
|
@classmethod
|
438
451
|
def get_documentation_url(cls) -> str:
|
@@ -490,15 +503,19 @@ class BaseWorker(abc.ABC):
|
|
490
503
|
return slugify(self.name)
|
491
504
|
|
492
505
|
def get_flow_run_logger(self, flow_run: "FlowRun") -> PrefectLogAdapter:
|
506
|
+
extra = {
|
507
|
+
"worker_name": self.name,
|
508
|
+
"work_pool_name": (
|
509
|
+
self._work_pool_name if self._work_pool else "<unknown>"
|
510
|
+
),
|
511
|
+
"work_pool_id": str(getattr(self._work_pool, "id", "unknown")),
|
512
|
+
}
|
513
|
+
if self.backend_id:
|
514
|
+
extra["worker_id"] = str(self.backend_id)
|
515
|
+
|
493
516
|
return flow_run_logger(flow_run=flow_run).getChild(
|
494
517
|
"worker",
|
495
|
-
extra=
|
496
|
-
"worker_name": self.name,
|
497
|
-
"work_pool_name": (
|
498
|
-
self._work_pool_name if self._work_pool else "<unknown>"
|
499
|
-
),
|
500
|
-
"work_pool_id": str(getattr(self._work_pool, "id", "unknown")),
|
501
|
-
},
|
518
|
+
extra=extra,
|
502
519
|
)
|
503
520
|
|
504
521
|
async def start(
|
@@ -712,54 +729,97 @@ class BaseWorker(abc.ABC):
|
|
712
729
|
|
713
730
|
self._work_pool = work_pool
|
714
731
|
|
715
|
-
async def
|
716
|
-
self, get_worker_id: bool = False
|
717
|
-
) -> Optional[UUID]:
|
732
|
+
async def _worker_metadata(self) -> Optional[WorkerMetadata]:
|
718
733
|
"""
|
719
|
-
|
720
|
-
|
721
|
-
If `get_worker_id` is True, the worker ID will be retrieved from the API.
|
734
|
+
Returns metadata about installed Prefect collections for the worker.
|
722
735
|
"""
|
723
|
-
|
724
|
-
return await self._client.send_worker_heartbeat(
|
725
|
-
work_pool_name=self._work_pool_name,
|
726
|
-
worker_name=self.name,
|
727
|
-
heartbeat_interval_seconds=self.heartbeat_interval_seconds,
|
728
|
-
get_worker_id=get_worker_id,
|
729
|
-
)
|
736
|
+
installed_integrations = load_prefect_collections().keys()
|
730
737
|
|
731
|
-
|
738
|
+
integration_versions = [
|
739
|
+
Integration(name=dist.metadata["Name"], version=dist.version)
|
740
|
+
for dist in distributions()
|
741
|
+
# PyPI packages often use dashes, but Python package names use underscores
|
742
|
+
# because they must be valid identifiers.
|
743
|
+
if dist.metadata.get("Name").replace("-", "_") in installed_integrations
|
744
|
+
]
|
745
|
+
|
746
|
+
if integration_versions:
|
747
|
+
return WorkerMetadata(integrations=integration_versions)
|
748
|
+
return None
|
749
|
+
|
750
|
+
async def _send_worker_heartbeat(self) -> Optional[UUID]:
|
732
751
|
"""
|
733
|
-
|
734
|
-
queues. Sends a worker heartbeat to the API.
|
752
|
+
Sends a heartbeat to the API.
|
735
753
|
"""
|
736
|
-
|
737
|
-
|
754
|
+
if not self._client:
|
755
|
+
self._logger.warning("Client has not been initialized; skipping heartbeat.")
|
756
|
+
return None
|
757
|
+
if not self._work_pool:
|
758
|
+
self._logger.debug("Worker has no work pool; skipping heartbeat.")
|
759
|
+
return None
|
760
|
+
|
761
|
+
should_get_worker_id = self._should_get_worker_id()
|
762
|
+
|
763
|
+
params = {
|
764
|
+
"work_pool_name": self._work_pool_name,
|
765
|
+
"worker_name": self.name,
|
766
|
+
"heartbeat_interval_seconds": self.heartbeat_interval_seconds,
|
767
|
+
"get_worker_id": should_get_worker_id,
|
768
|
+
}
|
738
769
|
if (
|
739
|
-
|
740
|
-
and
|
741
|
-
self._client.server_type == ServerType.CLOUD
|
742
|
-
or get_current_settings().testing.test_mode
|
743
|
-
)
|
744
|
-
and self.backend_id is None
|
770
|
+
self._client.server_type == ServerType.CLOUD
|
771
|
+
and not self._worker_metadata_sent
|
745
772
|
):
|
746
|
-
|
747
|
-
|
748
|
-
|
773
|
+
worker_metadata = await self._worker_metadata()
|
774
|
+
if worker_metadata:
|
775
|
+
params["worker_metadata"] = worker_metadata
|
776
|
+
self._worker_metadata_sent = True
|
749
777
|
|
750
|
-
|
778
|
+
worker_id = None
|
779
|
+
try:
|
780
|
+
worker_id = await self._client.send_worker_heartbeat(**params)
|
781
|
+
except httpx.HTTPStatusError as e:
|
782
|
+
if e.response.status_code == 422 and should_get_worker_id:
|
783
|
+
self._logger.warning(
|
784
|
+
"Failed to retrieve worker ID from the Prefect API server."
|
785
|
+
)
|
786
|
+
params["get_worker_id"] = False
|
787
|
+
worker_id = await self._client.send_worker_heartbeat(**params)
|
788
|
+
else:
|
789
|
+
raise e
|
751
790
|
|
752
|
-
if
|
791
|
+
if should_get_worker_id and worker_id is None:
|
753
792
|
self._logger.warning(
|
754
793
|
"Failed to retrieve worker ID from the Prefect API server."
|
755
794
|
)
|
756
|
-
|
795
|
+
|
796
|
+
return worker_id
|
797
|
+
|
798
|
+
async def sync_with_backend(self):
|
799
|
+
"""
|
800
|
+
Updates the worker's local information about it's current work pool and
|
801
|
+
queues. Sends a worker heartbeat to the API.
|
802
|
+
"""
|
803
|
+
await self._update_local_work_pool_info()
|
804
|
+
|
805
|
+
remote_id = await self._send_worker_heartbeat()
|
806
|
+
if remote_id:
|
757
807
|
self.backend_id = remote_id
|
808
|
+
self._logger = get_worker_logger(self)
|
758
809
|
|
759
810
|
self._logger.debug(
|
760
811
|
f"Worker synchronized with the Prefect API server. Remote ID: {self.backend_id}"
|
761
812
|
)
|
762
813
|
|
814
|
+
def _should_get_worker_id(self):
|
815
|
+
"""Determines if the worker should request an ID from the API server."""
|
816
|
+
return (
|
817
|
+
get_current_settings().experiments.worker_logging_to_api_enabled
|
818
|
+
and self._client
|
819
|
+
and self._client.server_type == ServerType.CLOUD
|
820
|
+
and self.backend_id is None
|
821
|
+
)
|
822
|
+
|
763
823
|
async def _get_scheduled_flow_runs(
|
764
824
|
self,
|
765
825
|
) -> List["WorkerFlowRunResponse"]:
|
@@ -813,6 +873,17 @@ class BaseWorker(abc.ABC):
|
|
813
873
|
run_logger.info(
|
814
874
|
f"Worker '{self.name}' submitting flow run '{flow_run.id}'"
|
815
875
|
)
|
876
|
+
if (
|
877
|
+
get_current_settings().experiments.worker_logging_to_api_enabled
|
878
|
+
and self.backend_id
|
879
|
+
):
|
880
|
+
worker_path = f"worker/{self.backend_id}"
|
881
|
+
base_url = url_for("work-pool", self._work_pool.id)
|
882
|
+
|
883
|
+
run_logger.info(
|
884
|
+
f"Running on worker id: {self.backend_id}. See worker logs here: {base_url}/{worker_path}"
|
885
|
+
)
|
886
|
+
|
816
887
|
self._submitting_flow_run_ids.add(flow_run.id)
|
817
888
|
self._runs_task_group.start_soon(
|
818
889
|
self._submit_run,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
prefect/.prefectignore,sha256=awSprvKT0vI8a64mEOLrMxhxqcO-b0ERQeYpA2rNKVQ,390
|
2
2
|
prefect/__init__.py,sha256=UZPTpdap8ECK1zLoggfeOtZGkKcf6um1-lMb-nn4o5I,3450
|
3
|
-
prefect/_version.py,sha256=
|
3
|
+
prefect/_version.py,sha256=l2zyL-F66iaGpmwpxB6wRww__gYWl19cPDqMKnOCZ2w,496
|
4
4
|
prefect/agent.py,sha256=BOVVY5z-vUIQ2u8LwMTXDaNys2fjOZSS5YGDwJmTQjI,230
|
5
5
|
prefect/artifacts.py,sha256=dsxFWmdg2r9zbHM3KgKOR5YbJ29_dXUYF9kipJpbxkE,13009
|
6
6
|
prefect/automations.py,sha256=NlQ62GPJzy-gnWQqX7c6CQJKw7p60WLGDAFcy82vtg4,5613
|
@@ -13,7 +13,7 @@ prefect/flow_engine.py,sha256=BuxfnKDqYcJvmVfPlAv8yMSwkDM25SBeJVk_MGQnm8k,30076
|
|
13
13
|
prefect/flow_runs.py,sha256=EaXRIQTOnwnA0fO7_EjwafFRmS57K_CRy0Xsz3JDIhc,16070
|
14
14
|
prefect/flows.py,sha256=PUEPpQx5pjXMVfnrPbIeAvWUdEdeaNPO4DXj7tgxGcA,90185
|
15
15
|
prefect/futures.py,sha256=DlZvdccKtwQKuDUFrZ4zcINeO9C1chLiNOwjE5gTgCk,16356
|
16
|
-
prefect/main.py,sha256=
|
16
|
+
prefect/main.py,sha256=TPktwZJ8a6l9U5A01a6x6wEZ7DqD4z7OWz0WadAPS6I,2441
|
17
17
|
prefect/plugins.py,sha256=HY7Z7OJlltqzsUiPMEL1Y_hQbHw0CeZKayWiK-k8DP4,2435
|
18
18
|
prefect/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
prefect/results.py,sha256=D8pjiiK0btWuTSQ4txw4glB4VFxvdrqef6z51-JgU5c,47670
|
@@ -32,6 +32,7 @@ prefect/_internal/integrations.py,sha256=U4cZMDbnilzZSKaMxvzZcSL27a1tzRMjDoTfr2u
|
|
32
32
|
prefect/_internal/pytz.py,sha256=WWl9x16rKFWequGmcOGs_ljpCDPf2LDHMyZp_4D8e6c,13748
|
33
33
|
prefect/_internal/retries.py,sha256=xtgj6oPSvYQLbyk451LR6swcRQvRVWEzCxY6GMK7qA4,2284
|
34
34
|
prefect/_internal/compatibility/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
35
|
+
prefect/_internal/compatibility/async_dispatch.py,sha256=QpfHgjdmLW4PXp-sBXIqUlGL0bJnXhtLBGXi7PWR8Bo,2174
|
35
36
|
prefect/_internal/compatibility/deprecated.py,sha256=PVME2C3Oe4_8tKIGufx1W4EpGkz5IQY8gFohPVOjNcM,7533
|
36
37
|
prefect/_internal/compatibility/migration.py,sha256=oifIj6mKwVUfrUO13ODLKOCwLON4YqaTXzhBFh8AK6E,6797
|
37
38
|
prefect/_internal/concurrency/__init__.py,sha256=YlTwU9ryjPNwbJa45adLJY00t_DGCh1QrdtY9WdVFfw,2140
|
@@ -69,13 +70,13 @@ prefect/client/base.py,sha256=2K8UiWzorZNNM4c8c-OiGeZ5i5ViUfZ_Q31oPobbOO0,24956
|
|
69
70
|
prefect/client/cloud.py,sha256=P8GctOSRqrukU_j9iXDuOW9lIiscRFP6WLxASDcmn_o,6207
|
70
71
|
prefect/client/collections.py,sha256=u-96saqu0RALAazRI0YaZCJahnuafMppY21KN6ggx80,1059
|
71
72
|
prefect/client/constants.py,sha256=Z_GG8KF70vbbXxpJuqW5pLnwzujTVeHbcYYRikNmGH0,29
|
72
|
-
prefect/client/orchestration.py,sha256=
|
73
|
+
prefect/client/orchestration.py,sha256=p3vblcSgrle4p-_Rl99GDz1sNzH-2pltxYUZHbFwUeM,151496
|
73
74
|
prefect/client/subscriptions.py,sha256=oqF2MJsgN3psJg-MePfvwMtEWjromfP9StWF00xc1eg,3403
|
74
75
|
prefect/client/utilities.py,sha256=89fmza0cRMOayxgXRdO51TKb11TczJ0ByOZmcZVrt44,3286
|
75
76
|
prefect/client/schemas/__init__.py,sha256=KlyqFV-hMulMkNstBn_0ijoHoIwJZaBj6B1r07UmgvE,607
|
76
77
|
prefect/client/schemas/actions.py,sha256=m2HJr9oAoK9vqigCHSwMGwvKPTjGNEtP0xdXgforIT0,28629
|
77
78
|
prefect/client/schemas/filters.py,sha256=ynzD3mdvHHkI51pJ_NWgeDv8Awr7-2QtpUq7fTInEYM,36316
|
78
|
-
prefect/client/schemas/objects.py,sha256=
|
79
|
+
prefect/client/schemas/objects.py,sha256=H3yeFyyV3LGRe6IA8Yj-1RxpVgSfqVhodOQ6YhuIcXI,56629
|
79
80
|
prefect/client/schemas/responses.py,sha256=tV06W8npA8oCjV9d0ZNvjro4QcbHxayb8PC4LmanXjo,15467
|
80
81
|
prefect/client/schemas/schedules.py,sha256=8rpqjOYtknu2-1n5_WD4cOplgu93P3mCyX86B22LfL4,13070
|
81
82
|
prefect/client/schemas/sorting.py,sha256=L-2Mx-igZPtsUoRUguTcG3nIEstMEMPD97NwPM2Ox5s,2579
|
@@ -137,10 +138,10 @@ prefect/logging/__init__.py,sha256=zx9f5_dWrR4DbcTOFBpNGOPoCZ1QcPFudr7zxb2XRpA,1
|
|
137
138
|
prefect/logging/configuration.py,sha256=5kOYdd4Hi6t-wJhxEncYS9bUAMEMTRKy_i22pepjDrw,3343
|
138
139
|
prefect/logging/filters.py,sha256=9keHLN4-cpnsWcii1qU0RITNi9-m7pOhkJ_t0MtCM4k,1117
|
139
140
|
prefect/logging/formatters.py,sha256=3nBWgawvD48slT0zgkKeus1gIyf0OjmDKdLwMEe5mPU,3923
|
140
|
-
prefect/logging/handlers.py,sha256=
|
141
|
+
prefect/logging/handlers.py,sha256=86uVPKimc6sBqpig6-nMSSbgXFkftvlKiRgZ7EtMqsw,11776
|
141
142
|
prefect/logging/highlighters.py,sha256=pH6TZmMaMdAkMLJB-U9NPWZIj3GUfczYCSFzCbBCq2s,1776
|
142
|
-
prefect/logging/loggers.py,sha256=
|
143
|
-
prefect/logging/logging.yml,sha256
|
143
|
+
prefect/logging/loggers.py,sha256=BKYjyP8N2aJPdHwpqNU5Od4uu0pKztsDQ7RPRNb4oGo,12146
|
144
|
+
prefect/logging/logging.yml,sha256=-HoWPYLsKMt5bKRgjIqk8S7tQlAaQ93pGkwIkYtMVMo,3172
|
144
145
|
prefect/records/__init__.py,sha256=rJJhaJBa0AWu63fJhtB-yHBi64qL6p4svo7F0qvm2sc,30
|
145
146
|
prefect/records/base.py,sha256=Ne-7pRGNfmk0a_Vm3t5zRrj26KgGr_L2_XfLID0XzIY,8035
|
146
147
|
prefect/records/filesystem.py,sha256=X-h7r5deiHH5IaaDk4ugOCmR5ZKnJeU2cLgp0AkMt0E,7316
|
@@ -165,19 +166,19 @@ prefect/settings/context.py,sha256=yKxnaDJHX8e2jmAVtw1RF9o7X4V3AOcz61sVeQyPX2c,2
|
|
165
166
|
prefect/settings/legacy.py,sha256=GcaB1mkahc4HbjnUvr4_3qNNxleke08dYnOXCUt8w3k,5617
|
166
167
|
prefect/settings/profiles.py,sha256=VZdzOV-KSuAkCxtdhBmSG9i84-K2QLSx6g2-vIUkfig,12169
|
167
168
|
prefect/settings/profiles.toml,sha256=kTvqDNMzjH3fsm5OEI-NKY4dMmipor5EvQXRB6rPEjY,522
|
168
|
-
prefect/settings/sources.py,sha256=
|
169
|
+
prefect/settings/sources.py,sha256=ixt4dTP766s2nr_IjPKS81ZneAaiWzw93zjrWCnkKp8,9345
|
169
170
|
prefect/settings/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
170
|
-
prefect/settings/models/api.py,sha256=
|
171
|
+
prefect/settings/models/api.py,sha256=DuoBHYeMX9MlgjT6JNLWPHSj_iySo98a0uNuTVSk1r8,1511
|
171
172
|
prefect/settings/models/cli.py,sha256=mHB-BHBVO9qfQcr9uHbBmU6MMDLlLUUDxjyaRz7v1ls,958
|
172
173
|
prefect/settings/models/client.py,sha256=Ua18wKXWrNi0Ktb6cd42kdSOQYhFnObwTHTc3QDYg2k,2985
|
173
174
|
prefect/settings/models/cloud.py,sha256=7-WtW0rGPMOuG1O9Lwv_H_keEldXPo9YGHmoGwCXpeg,1767
|
174
175
|
prefect/settings/models/deployments.py,sha256=ui4xqT7N-6B_gMnGnkJ8Ys0hzdfNTOtlcwzAQo2LlQI,1235
|
175
|
-
prefect/settings/models/experiments.py,sha256=
|
176
|
+
prefect/settings/models/experiments.py,sha256=CXezPxuyV4pTxvQ9xroUmpEudXbDgUv-Y56A16307Wc,862
|
176
177
|
prefect/settings/models/flows.py,sha256=IrW3cFQJnu-31cXvSihCl6naRhmZc2eFqSgqdnNLUzg,1056
|
177
178
|
prefect/settings/models/internal.py,sha256=mJ7h3d_WHw-0FBVhIbDHPjYI041YtuReD1bZO98G1OU,612
|
178
|
-
prefect/settings/models/logging.py,sha256=
|
179
|
+
prefect/settings/models/logging.py,sha256=YxD8fOcWOROUrq6dH6EIadJwYL7LlLSUGWlHVLn-l3Y,4560
|
179
180
|
prefect/settings/models/results.py,sha256=lU-3oVlgxGradSB1EYU-VZogK-rzE5LDw_wOcKhJp4M,1397
|
180
|
-
prefect/settings/models/root.py,sha256=
|
181
|
+
prefect/settings/models/root.py,sha256=7PC_Oiz7SYM3Ea3rqn_XnozvdivNq3rKc2dEcjTA5L4,16377
|
181
182
|
prefect/settings/models/runner.py,sha256=TOYDnmYmv6Ec5Ma6Vii-rPUTeMGszDluecLwy65Gnm4,1644
|
182
183
|
prefect/settings/models/tasks.py,sha256=qXLWJIVZT4L1GQcaCD_NUJktpB5WYOxthOq_PDLdj20,3309
|
183
184
|
prefect/settings/models/testing.py,sha256=r6KCYi8nvflCEwRFqS2T5J9baMHtVJfW0Q8EWARu1PQ,1733
|
@@ -187,13 +188,18 @@ prefect/settings/models/server/api.py,sha256=EKpt31hlX9dwrrsW-QztuJCHfUXqeWSz5xA
|
|
187
188
|
prefect/settings/models/server/database.py,sha256=0eerMb05A9wD9_C4SefTzVvmba3rW18K2eteL2IcXLg,7201
|
188
189
|
prefect/settings/models/server/deployments.py,sha256=_GcxGOsMMrCzGEnOwC2i6JQW77h2tbyAdBJzAigHDas,778
|
189
190
|
prefect/settings/models/server/ephemeral.py,sha256=WxSpF-z9iDKAEjvcqrA5aggLEPRbl_ERocoLxPlu424,923
|
190
|
-
prefect/settings/models/server/events.py,sha256=
|
191
|
+
prefect/settings/models/server/events.py,sha256=UJ-cQdTvkYOxU5WCTx3EebTv01E_gDaA-3ni8jwyjN8,5147
|
191
192
|
prefect/settings/models/server/flow_run_graph.py,sha256=MCKKV91-I6AOAGJ9ieTvHIMqH5JEri7cfuDbadEFu4w,1026
|
192
193
|
prefect/settings/models/server/root.py,sha256=uhryCvrk6oGAu8S3GGZNc3IaxXyd_dFnsKfi1q8En_U,5128
|
193
194
|
prefect/settings/models/server/services.py,sha256=xDQyRUh18mU_cTgp1Q_BwKpdeZHKLSh2o_YNNXvzsoo,16748
|
194
195
|
prefect/settings/models/server/tasks.py,sha256=YzfRTcmiYnD_INezR_29rvO0M_YbAqT1by7kiK2KP60,2814
|
195
196
|
prefect/settings/models/server/ui.py,sha256=6cTSC2RQsS4c2HpB1Se6qRms4RMEKSsaI40T2CTkobg,1780
|
196
|
-
prefect/
|
197
|
+
prefect/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
198
|
+
prefect/telemetry/bootstrap.py,sha256=oOdEjIHDQHANviyP2ZKpz9B4yBq832f9y1PKwAQ5-ro,987
|
199
|
+
prefect/telemetry/instrumentation.py,sha256=S7IRYqKXaixAt-W5oxDO4CqkyXc3dy4ATgBmuindJIM,4241
|
200
|
+
prefect/telemetry/logging.py,sha256=yn5D4D2GGRrAv0y8wlHPN7PZDmQucGjQT_YauK9M9Yo,727
|
201
|
+
prefect/telemetry/processors.py,sha256=5vxjk9m-0FxqGDmVySY_u4zceZ9mwmTPiSFuCDOyGWA,2006
|
202
|
+
prefect/types/__init__.py,sha256=oabzKSvuT7tKBX6OiBev40wUtN1JstJT2L7ocpEUZ14,3640
|
197
203
|
prefect/types/entrypoint.py,sha256=2FF03-wLPgtnqR_bKJDB2BsXXINPdu8ptY9ZYEZnXg8,328
|
198
204
|
prefect/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
199
205
|
prefect/utilities/annotations.py,sha256=Ocj2s5zhnGr8uXUBnOli-OrybXVJdu4-uZvCRpKpV_Q,2820
|
@@ -224,14 +230,14 @@ prefect/utilities/schema_tools/__init__.py,sha256=KsFsTEHQqgp89TkDpjggkgBBywoHQP
|
|
224
230
|
prefect/utilities/schema_tools/hydration.py,sha256=k12qVCdLLrK-mNo1hPCdhxM5f_N14Nj0vJdtiWYWffk,8858
|
225
231
|
prefect/utilities/schema_tools/validation.py,sha256=2GCjxwApTFwzey40ul9OkcAXrU3r-kWK__9ucMo0qbk,9744
|
226
232
|
prefect/workers/__init__.py,sha256=8dP8SLZbWYyC_l9DRTQSE3dEbDgns5DZDhxkp_NfsbQ,35
|
227
|
-
prefect/workers/base.py,sha256=
|
233
|
+
prefect/workers/base.py,sha256=ChdMddrKvpmk08m-UQXiP2R4xCIfsiej-0zD2jt9llc,47795
|
228
234
|
prefect/workers/block.py,sha256=BOVVY5z-vUIQ2u8LwMTXDaNys2fjOZSS5YGDwJmTQjI,230
|
229
235
|
prefect/workers/cloud.py,sha256=BOVVY5z-vUIQ2u8LwMTXDaNys2fjOZSS5YGDwJmTQjI,230
|
230
236
|
prefect/workers/process.py,sha256=tcJ3fbiraLCfpVGpv8dOHwMSfVzeD_kyguUOvPuIz6I,19796
|
231
237
|
prefect/workers/server.py,sha256=lgh2FfSuaNU7b6HPxSFm8JtKvAvHsZGkiOo4y4tW1Cw,2022
|
232
238
|
prefect/workers/utilities.py,sha256=VfPfAlGtTuDj0-Kb8WlMgAuOfgXCdrGAnKMapPSBrwc,2483
|
233
|
-
prefect_client-3.1.
|
234
|
-
prefect_client-3.1.
|
235
|
-
prefect_client-3.1.
|
236
|
-
prefect_client-3.1.
|
237
|
-
prefect_client-3.1.
|
239
|
+
prefect_client-3.1.1.dist-info/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
|
240
|
+
prefect_client-3.1.1.dist-info/METADATA,sha256=OlGsslMyT1YoImmLFDnko6XXPcvfqJV__WBbd6KClG4,7338
|
241
|
+
prefect_client-3.1.1.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
|
242
|
+
prefect_client-3.1.1.dist-info/top_level.txt,sha256=MJZYJgFdbRc2woQCeB4vM6T33tr01TmkEhRcns6H_H4,8
|
243
|
+
prefect_client-3.1.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|