prefect-client 3.0.3__py3-none-any.whl → 3.0.5__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/retries.py +1 -3
- prefect/_internal/schemas/validators.py +1 -1
- prefect/cache_policies.py +1 -1
- prefect/client/cloud.py +9 -0
- prefect/client/orchestration.py +30 -2
- prefect/client/schemas/objects.py +17 -2
- prefect/client/subscriptions.py +3 -3
- prefect/context.py +11 -19
- prefect/deployments/base.py +12 -0
- prefect/deployments/flow_runs.py +8 -0
- prefect/events/clients.py +40 -22
- prefect/exceptions.py +22 -3
- prefect/filesystems.py +26 -1
- prefect/flow_engine.py +10 -7
- prefect/flows.py +11 -2
- prefect/logging/configuration.py +4 -8
- prefect/logging/handlers.py +3 -4
- prefect/results.py +73 -7
- prefect/runner/runner.py +5 -37
- prefect/settings.py +1364 -1582
- prefect/transactions.py +9 -9
- prefect/types/__init__.py +51 -1
- prefect/utilities/pydantic.py +2 -1
- prefect/utilities/text.py +13 -1
- prefect/workers/base.py +7 -56
- {prefect_client-3.0.3.dist-info → prefect_client-3.0.5.dist-info}/METADATA +1 -1
- {prefect_client-3.0.3.dist-info → prefect_client-3.0.5.dist-info}/RECORD +30 -31
- prefect/_internal/compatibility/experimental.py +0 -195
- {prefect_client-3.0.3.dist-info → prefect_client-3.0.5.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.3.dist-info → prefect_client-3.0.5.dist-info}/WHEEL +0 -0
- {prefect_client-3.0.3.dist-info → prefect_client-3.0.5.dist-info}/top_level.txt +0 -0
prefect/settings.py
CHANGED
@@ -1,59 +1,28 @@
|
|
1
1
|
"""
|
2
|
-
Prefect settings
|
2
|
+
Prefect settings are defined using `BaseSettings` from `pydantic_settings`. `BaseSettings` can load setting values
|
3
|
+
from system environment variables and each additionally specified `env_file`.
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
All settings defined in this file are used to generate a dynamic Pydantic settings class
|
8
|
-
called `Settings`. When instantiated, this class will load settings from environment
|
9
|
-
variables and pull default values from the setting definitions.
|
10
|
-
|
11
|
-
The current instance of `Settings` being used by the application is stored in a
|
12
|
-
`SettingsContext` model which allows each instance of the `Settings` class to be
|
13
|
-
accessed in an async-safe manner.
|
14
|
-
|
15
|
-
Aside from environment variables, we allow settings to be changed during the runtime of
|
16
|
-
the process using profiles. Profiles contain setting overrides that the user may
|
17
|
-
persist without setting environment variables. Profiles are also used internally for
|
18
|
-
managing settings during task run execution where differing settings may be used
|
19
|
-
concurrently in the same process and during testing where we need to override settings
|
20
|
-
to ensure their value is respected as intended.
|
21
|
-
|
22
|
-
The `SettingsContext` is set when the `prefect` module is imported. This context is
|
23
|
-
referred to as the "root" settings context for clarity. Generally, this is the only
|
24
|
-
settings context that will be used. When this context is entered, we will instantiate
|
25
|
-
a `Settings` object, loading settings from environment variables and defaults, then we
|
26
|
-
will load the active profile and use it to override settings. See `enter_root_settings_context`
|
27
|
-
for details on determining the active profile.
|
28
|
-
|
29
|
-
Another `SettingsContext` may be entered at any time to change the settings being
|
30
|
-
used by the code within the context. Generally, users should not use this. Settings
|
31
|
-
management should be left to Prefect application internals.
|
32
|
-
|
33
|
-
Generally, settings should be accessed with `SETTING_VARIABLE.value()` which will
|
34
|
-
pull the current `Settings` instance from the current `SettingsContext` and retrieve
|
35
|
-
the value of the relevant setting.
|
36
|
-
|
37
|
-
Accessing a setting's value will also call the `Setting.value_callback` which allows
|
38
|
-
settings to be dynamically modified on retrieval. This allows us to make settings
|
39
|
-
dependent on the value of other settings or perform other dynamic effects.
|
5
|
+
The recommended user-facing way to access Prefect settings at this time is to import specific setting objects directly,
|
6
|
+
like `from prefect.settings import PREFECT_API_URL; print(PREFECT_API_URL.value())`.
|
40
7
|
|
8
|
+
Importantly, we replace the `callback` mechanism for updating settings with an "after" model_validator that updates dependent settings.
|
9
|
+
After https://github.com/pydantic/pydantic/issues/9789 is resolved, we will be able to define context-aware defaults
|
10
|
+
for settings, at which point we will not need to use the "after" model_validator.
|
41
11
|
"""
|
42
12
|
|
43
|
-
import logging
|
44
13
|
import os
|
45
14
|
import re
|
46
|
-
import
|
15
|
+
import sys
|
47
16
|
import warnings
|
48
17
|
from contextlib import contextmanager
|
49
18
|
from datetime import timedelta
|
19
|
+
from functools import partial
|
50
20
|
from pathlib import Path
|
51
21
|
from typing import (
|
22
|
+
Annotated,
|
52
23
|
Any,
|
53
|
-
Callable,
|
54
24
|
Dict,
|
55
25
|
Generator,
|
56
|
-
Generic,
|
57
26
|
Iterable,
|
58
27
|
List,
|
59
28
|
Mapping,
|
@@ -63,174 +32,113 @@ from typing import (
|
|
63
32
|
Type,
|
64
33
|
TypeVar,
|
65
34
|
Union,
|
35
|
+
get_args,
|
66
36
|
)
|
67
|
-
from urllib.parse import urlparse
|
37
|
+
from urllib.parse import quote_plus, urlparse
|
68
38
|
|
69
|
-
import pydantic
|
70
39
|
import toml
|
71
40
|
from pydantic import (
|
41
|
+
AfterValidator,
|
72
42
|
BaseModel,
|
43
|
+
BeforeValidator,
|
73
44
|
ConfigDict,
|
74
45
|
Field,
|
75
|
-
|
76
|
-
|
77
|
-
|
46
|
+
Secret,
|
47
|
+
SecretStr,
|
48
|
+
SerializationInfo,
|
49
|
+
SerializerFunctionWrapHandler,
|
50
|
+
TypeAdapter,
|
51
|
+
ValidationError,
|
52
|
+
model_serializer,
|
78
53
|
model_validator,
|
79
54
|
)
|
80
|
-
from
|
81
|
-
from
|
55
|
+
from pydantic.fields import FieldInfo
|
56
|
+
from pydantic_settings import (
|
57
|
+
BaseSettings,
|
58
|
+
PydanticBaseSettingsSource,
|
59
|
+
SettingsConfigDict,
|
60
|
+
)
|
61
|
+
from typing_extensions import Literal, Self
|
82
62
|
|
83
|
-
from prefect.
|
84
|
-
from prefect.
|
85
|
-
from prefect.
|
86
|
-
from prefect.utilities.
|
87
|
-
from prefect.utilities.pydantic import add_cloudpickle_reduction
|
63
|
+
from prefect.exceptions import ProfileSettingsValidationError
|
64
|
+
from prefect.types import ClientRetryExtraCodes, LogLevel
|
65
|
+
from prefect.utilities.collections import visit_collection
|
66
|
+
from prefect.utilities.pydantic import handle_secret_render
|
88
67
|
|
89
68
|
T = TypeVar("T")
|
90
69
|
|
91
|
-
|
70
|
+
DEFAULT_PREFECT_HOME = Path.home() / ".prefect"
|
92
71
|
DEFAULT_PROFILES_PATH = Path(__file__).parent.joinpath("profiles.toml")
|
72
|
+
_SECRET_TYPES: Tuple[Type, ...] = (Secret, SecretStr)
|
93
73
|
|
94
74
|
|
95
|
-
|
75
|
+
def env_var_to_attr_name(env_var: str) -> str:
|
96
76
|
"""
|
97
|
-
|
77
|
+
Convert an environment variable name to an attribute name.
|
98
78
|
"""
|
79
|
+
return env_var.replace("PREFECT_", "").lower()
|
99
80
|
|
100
|
-
def __init__(
|
101
|
-
self,
|
102
|
-
type: Type[T],
|
103
|
-
*,
|
104
|
-
deprecated: bool = False,
|
105
|
-
deprecated_start_date: Optional[str] = None,
|
106
|
-
deprecated_end_date: Optional[str] = None,
|
107
|
-
deprecated_help: str = "",
|
108
|
-
deprecated_when_message: str = "",
|
109
|
-
deprecated_when: Optional[Callable[[Any], bool]] = None,
|
110
|
-
deprecated_renamed_to: Optional["Setting[T]"] = None,
|
111
|
-
value_callback: Optional[Callable[["Settings", T], T]] = None,
|
112
|
-
is_secret: bool = False,
|
113
|
-
**kwargs: Any,
|
114
|
-
) -> None:
|
115
|
-
self.field: fields.FieldInfo = Field(**kwargs)
|
116
|
-
self.type = type
|
117
|
-
self.value_callback = value_callback
|
118
|
-
self._name = None
|
119
|
-
self.is_secret = is_secret
|
120
|
-
self.deprecated = deprecated
|
121
|
-
self.deprecated_start_date = deprecated_start_date
|
122
|
-
self.deprecated_end_date = deprecated_end_date
|
123
|
-
self.deprecated_help = deprecated_help
|
124
|
-
self.deprecated_when = deprecated_when or (lambda _: True)
|
125
|
-
self.deprecated_when_message = deprecated_when_message
|
126
|
-
self.deprecated_renamed_to = deprecated_renamed_to
|
127
|
-
self.deprecated_renamed_from = None
|
128
|
-
self.__doc__ = self.field.description
|
129
|
-
|
130
|
-
# Validate the deprecation settings, will throw an error at setting definition
|
131
|
-
# time if the developer has not configured it correctly
|
132
|
-
if deprecated:
|
133
|
-
generate_deprecation_message(
|
134
|
-
name="...", # setting names not populated until after init
|
135
|
-
start_date=self.deprecated_start_date,
|
136
|
-
end_date=self.deprecated_end_date,
|
137
|
-
help=self.deprecated_help,
|
138
|
-
when=self.deprecated_when_message,
|
139
|
-
)
|
140
81
|
|
141
|
-
|
142
|
-
|
143
|
-
|
82
|
+
def is_test_mode() -> bool:
|
83
|
+
"""Check if the current process is in test mode."""
|
84
|
+
return bool(os.getenv("PREFECT_TEST_MODE") or os.getenv("PREFECT_UNIT_TEST_MODE"))
|
144
85
|
|
145
|
-
def value(self, bypass_callback: bool = False) -> T:
|
146
|
-
"""
|
147
|
-
Get the current value of a setting.
|
148
86
|
|
149
|
-
|
150
|
-
|
151
|
-
from prefect.settings import PREFECT_API_URL
|
152
|
-
PREFECT_API_URL.value()
|
153
|
-
```
|
154
|
-
"""
|
155
|
-
return self.value_from(get_current_settings(), bypass_callback=bypass_callback)
|
156
|
-
|
157
|
-
def value_from(self, settings: "Settings", bypass_callback: bool = False) -> T:
|
158
|
-
"""
|
159
|
-
Get the value of a setting from a settings object
|
160
|
-
|
161
|
-
Example:
|
162
|
-
```python
|
163
|
-
from prefect.settings import get_default_settings
|
164
|
-
PREFECT_API_URL.value_from(get_default_settings())
|
165
|
-
```
|
166
|
-
"""
|
167
|
-
value = settings.value_of(self, bypass_callback=bypass_callback)
|
168
|
-
|
169
|
-
if not bypass_callback and self.deprecated and self.deprecated_when(value):
|
170
|
-
# Check if this setting is deprecated and someone is accessing the value
|
171
|
-
# via the old name
|
172
|
-
warnings.warn(self.deprecated_message, DeprecationWarning, stacklevel=3)
|
173
|
-
|
174
|
-
# If the the value is empty, return the new setting's value for compat
|
175
|
-
if value is None and self.deprecated_renamed_to is not None:
|
176
|
-
return self.deprecated_renamed_to.value_from(settings)
|
177
|
-
|
178
|
-
if not bypass_callback and self.deprecated_renamed_from is not None:
|
179
|
-
# Check if this setting is a rename of a deprecated setting and the
|
180
|
-
# deprecated setting is set and should be used for compatibility
|
181
|
-
deprecated_value = self.deprecated_renamed_from.value_from(
|
182
|
-
settings, bypass_callback=True
|
183
|
-
)
|
184
|
-
if deprecated_value is not None:
|
185
|
-
warnings.warn(
|
186
|
-
(
|
187
|
-
f"{self.deprecated_renamed_from.deprecated_message} Because"
|
188
|
-
f" {self.deprecated_renamed_from.name!r} is set it will be used"
|
189
|
-
f" instead of {self.name!r} for backwards compatibility."
|
190
|
-
),
|
191
|
-
DeprecationWarning,
|
192
|
-
stacklevel=3,
|
193
|
-
)
|
194
|
-
return deprecated_value or value
|
87
|
+
class Setting:
|
88
|
+
"""Mimics the old Setting object for compatibility with existing code."""
|
195
89
|
|
196
|
-
|
90
|
+
def __init__(self, name: str, default: Any, type_: Any):
|
91
|
+
self._name = name
|
92
|
+
self._default = default
|
93
|
+
self._type = type_
|
197
94
|
|
198
95
|
@property
|
199
96
|
def name(self):
|
200
|
-
|
201
|
-
return self._name
|
202
|
-
|
203
|
-
# Lookup the name on first access
|
204
|
-
for name, val in tuple(globals().items()):
|
205
|
-
if val == self:
|
206
|
-
self._name = name
|
207
|
-
return name
|
208
|
-
|
209
|
-
raise ValueError("Setting not found in `prefect.settings` module.")
|
210
|
-
|
211
|
-
@name.setter
|
212
|
-
def name(self, value: str):
|
213
|
-
self._name = value
|
97
|
+
return self._name
|
214
98
|
|
215
99
|
@property
|
216
|
-
def
|
217
|
-
return
|
218
|
-
name=f"Setting {self.name!r}",
|
219
|
-
start_date=self.deprecated_start_date,
|
220
|
-
end_date=self.deprecated_end_date,
|
221
|
-
help=self.deprecated_help,
|
222
|
-
when=self.deprecated_when_message,
|
223
|
-
)
|
100
|
+
def field_name(self):
|
101
|
+
return env_var_to_attr_name(self.name)
|
224
102
|
|
225
|
-
|
226
|
-
|
103
|
+
@property
|
104
|
+
def is_secret(self):
|
105
|
+
if self._type in _SECRET_TYPES:
|
106
|
+
return True
|
107
|
+
for secret_type in _SECRET_TYPES:
|
108
|
+
if secret_type in get_args(self._type):
|
109
|
+
return True
|
110
|
+
return False
|
111
|
+
|
112
|
+
def default(self):
|
113
|
+
return self._default
|
114
|
+
|
115
|
+
def value(self: Self) -> Any:
|
116
|
+
if self.name == "PREFECT_TEST_SETTING":
|
117
|
+
if "PREFECT_TEST_MODE" in os.environ:
|
118
|
+
return get_current_settings().test_setting
|
119
|
+
else:
|
120
|
+
return None
|
121
|
+
|
122
|
+
current_value = getattr(get_current_settings(), self.field_name)
|
123
|
+
if isinstance(current_value, _SECRET_TYPES):
|
124
|
+
return current_value.get_secret_value()
|
125
|
+
return current_value
|
126
|
+
|
127
|
+
def value_from(self: Self, settings: "Settings") -> Any:
|
128
|
+
current_value = getattr(settings, self.field_name)
|
129
|
+
if isinstance(current_value, _SECRET_TYPES):
|
130
|
+
return current_value.get_secret_value()
|
131
|
+
return current_value
|
227
132
|
|
228
133
|
def __bool__(self) -> bool:
|
229
|
-
"""
|
230
|
-
Returns a truthy check of the current value.
|
231
|
-
"""
|
232
134
|
return bool(self.value())
|
233
135
|
|
136
|
+
def __str__(self) -> str:
|
137
|
+
return str(self.value())
|
138
|
+
|
139
|
+
def __repr__(self) -> str:
|
140
|
+
return f"<{self.name}: {self._type!r}>"
|
141
|
+
|
234
142
|
def __eq__(self, __o: object) -> bool:
|
235
143
|
return __o.__eq__(self.value())
|
236
144
|
|
@@ -238,108 +146,63 @@ class Setting(Generic[T]):
|
|
238
146
|
return hash((type(self), self.name))
|
239
147
|
|
240
148
|
|
241
|
-
|
242
|
-
|
149
|
+
########################################################################
|
150
|
+
# Define post init validators for use in an "after" model_validator,
|
151
|
+
# core logic will remain similar after context-aware defaults are supported
|
243
152
|
|
244
|
-
def get_extra_loggers(_: "Settings", value: str) -> List[str]:
|
245
|
-
"""
|
246
|
-
`value_callback` for `PREFECT_LOGGING_EXTRA_LOGGERS`that parses the CSV string into a
|
247
|
-
list and trims whitespace from logger names.
|
248
|
-
"""
|
249
|
-
return [name.strip() for name in value.split(",")] if value else []
|
250
|
-
|
251
|
-
|
252
|
-
def expanduser_in_path(_, value: Path) -> Path:
|
253
|
-
return value.expanduser()
|
254
153
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
`value_callback` for `PREFECT_LOGGING_LEVEL` that overrides the log level to DEBUG
|
259
|
-
when debug mode is enabled.
|
260
|
-
"""
|
261
|
-
if PREFECT_DEBUG_MODE.value_from(settings):
|
262
|
-
return "DEBUG"
|
263
|
-
else:
|
154
|
+
def default_ui_url(settings: "Settings") -> Optional[str]:
|
155
|
+
value = settings.ui_url
|
156
|
+
if value is not None:
|
264
157
|
return value
|
265
158
|
|
159
|
+
# Otherwise, infer a value from the API URL
|
160
|
+
ui_url = api_url = settings.api_url
|
266
161
|
|
267
|
-
|
268
|
-
"""
|
269
|
-
`value_callback` for `PREFECT_TEST_SETTING` that only allows access during test mode
|
270
|
-
"""
|
271
|
-
if PREFECT_TEST_MODE.value_from(settings):
|
272
|
-
return value
|
273
|
-
else:
|
162
|
+
if not api_url:
|
274
163
|
return None
|
275
164
|
|
165
|
+
cloud_url = settings.cloud_api_url
|
166
|
+
cloud_ui_url = settings.cloud_ui_url
|
167
|
+
if api_url.startswith(cloud_url):
|
168
|
+
ui_url = ui_url.replace(cloud_url, cloud_ui_url)
|
276
169
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
relative path '/api', otherwise it constructs an API URL from the API settings.
|
281
|
-
"""
|
282
|
-
if value is None:
|
283
|
-
# Set a default value
|
284
|
-
value = "/api"
|
285
|
-
|
286
|
-
return template_with_settings(
|
287
|
-
PREFECT_SERVER_API_HOST, PREFECT_SERVER_API_PORT, PREFECT_API_URL
|
288
|
-
)(settings, value)
|
289
|
-
|
170
|
+
if ui_url.endswith("/api"):
|
171
|
+
# Handles open-source APIs
|
172
|
+
ui_url = ui_url[:-4]
|
290
173
|
|
291
|
-
|
292
|
-
"""
|
293
|
-
`value_callback` for `PREFECT_CLIENT_RETRY_EXTRA_CODES` that ensures status codes
|
294
|
-
are integers in the range 100-599.
|
295
|
-
"""
|
296
|
-
if value == "":
|
297
|
-
return set()
|
174
|
+
# Handles Cloud APIs with content after `/api`
|
175
|
+
ui_url = ui_url.replace("/api/", "/")
|
298
176
|
|
299
|
-
|
177
|
+
# Update routing
|
178
|
+
ui_url = ui_url.replace("/accounts/", "/account/")
|
179
|
+
ui_url = ui_url.replace("/workspaces/", "/workspace/")
|
300
180
|
|
301
|
-
|
302
|
-
raise ValueError(
|
303
|
-
"PREFECT_CLIENT_RETRY_EXTRA_CODES must be a comma separated list of "
|
304
|
-
"integers between 100 and 599."
|
305
|
-
)
|
181
|
+
return ui_url
|
306
182
|
|
307
|
-
values = {int(v) for v in values}
|
308
|
-
return values
|
309
183
|
|
184
|
+
def default_cloud_ui_url(settings) -> Optional[str]:
|
185
|
+
value = settings.cloud_ui_url
|
186
|
+
if value is not None:
|
187
|
+
return value
|
310
188
|
|
311
|
-
|
312
|
-
|
313
|
-
Returns a `value_callback` that will template the given settings into the runtime
|
314
|
-
value for the setting.
|
315
|
-
"""
|
189
|
+
# Otherwise, infer a value from the API URL
|
190
|
+
ui_url = api_url = settings.cloud_api_url
|
316
191
|
|
317
|
-
|
318
|
-
|
319
|
-
return value # Do not attempt to template a null string
|
192
|
+
if re.match(r"^https://api[\.\w]*.prefect.[^\.]+/", api_url):
|
193
|
+
ui_url = ui_url.replace("https://api", "https://app", 1)
|
320
194
|
|
321
|
-
|
322
|
-
|
323
|
-
setting.name: setting.value_from(settings) for setting in upstream_settings
|
324
|
-
}
|
325
|
-
template = string.Template(str(value))
|
326
|
-
# Note the use of `safe_substitute` to avoid raising an exception if a
|
327
|
-
# template value is missing. In this case, template values will be left
|
328
|
-
# as-is in the string. Using `safe_substitute` prevents us raising when
|
329
|
-
# the DB password contains a `$` character.
|
330
|
-
return original_type(template.safe_substitute(template_values))
|
195
|
+
if ui_url.endswith("/api"):
|
196
|
+
ui_url = ui_url[:-4]
|
331
197
|
|
332
|
-
return
|
198
|
+
return ui_url
|
333
199
|
|
334
200
|
|
335
201
|
def max_log_size_smaller_than_batch_size(values):
|
336
202
|
"""
|
337
203
|
Validator for settings asserting the batch size and match log size are compatible
|
338
204
|
"""
|
339
|
-
if
|
340
|
-
values["PREFECT_LOGGING_TO_API_BATCH_SIZE"]
|
341
|
-
< values["PREFECT_LOGGING_TO_API_MAX_LOG_SIZE"]
|
342
|
-
):
|
205
|
+
if values["logging_to_api_batch_size"] < values["logging_to_api_max_log_size"]:
|
343
206
|
raise ValueError(
|
344
207
|
"`PREFECT_LOGGING_TO_API_MAX_LOG_SIZE` cannot be larger than"
|
345
208
|
" `PREFECT_LOGGING_TO_API_BATCH_SIZE`"
|
@@ -351,15 +214,22 @@ def warn_on_database_password_value_without_usage(values):
|
|
351
214
|
"""
|
352
215
|
Validator for settings warning if the database password is set but not used.
|
353
216
|
"""
|
354
|
-
|
217
|
+
db_password = (
|
218
|
+
v.get_secret_value()
|
219
|
+
if (v := values["api_database_password"]) and hasattr(v, "get_secret_value")
|
220
|
+
else None
|
221
|
+
)
|
222
|
+
api_db_connection_url = (
|
223
|
+
values["api_database_connection_url"].get_secret_value()
|
224
|
+
if hasattr(values["api_database_connection_url"], "get_secret_value")
|
225
|
+
else values["api_database_connection_url"]
|
226
|
+
)
|
355
227
|
if (
|
356
|
-
|
357
|
-
and not
|
358
|
-
and
|
359
|
-
and
|
360
|
-
|
361
|
-
not in values["PREFECT_API_DATABASE_CONNECTION_URL"]
|
362
|
-
)
|
228
|
+
db_password
|
229
|
+
and api_db_connection_url is not None
|
230
|
+
and ("PREFECT_API_DATABASE_PASSWORD" not in api_db_connection_url)
|
231
|
+
and db_password not in api_db_connection_url
|
232
|
+
and quote_plus(db_password) not in api_db_connection_url
|
363
233
|
):
|
364
234
|
warnings.warn(
|
365
235
|
"PREFECT_API_DATABASE_PASSWORD is set but not included in the "
|
@@ -373,7 +243,7 @@ def warn_on_misconfigured_api_url(values):
|
|
373
243
|
"""
|
374
244
|
Validator for settings warning if the API URL is misconfigured.
|
375
245
|
"""
|
376
|
-
api_url = values["
|
246
|
+
api_url = values["api_url"]
|
377
247
|
if api_url is not None:
|
378
248
|
misconfigured_mappings = {
|
379
249
|
"app.prefect.cloud": (
|
@@ -408,1382 +278,1267 @@ def warn_on_misconfigured_api_url(values):
|
|
408
278
|
return values
|
409
279
|
|
410
280
|
|
411
|
-
def default_database_connection_url(settings: "Settings"
|
412
|
-
|
413
|
-
if
|
281
|
+
def default_database_connection_url(settings: "Settings") -> SecretStr:
|
282
|
+
value = None
|
283
|
+
if settings.api_database_driver == "postgresql+asyncpg":
|
414
284
|
required = [
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
]
|
420
|
-
missing = [
|
421
|
-
setting.name for setting in required if not setting.value_from(settings)
|
285
|
+
"api_database_host",
|
286
|
+
"api_database_user",
|
287
|
+
"api_database_name",
|
288
|
+
"api_database_password",
|
422
289
|
]
|
290
|
+
missing = [attr for attr in required if getattr(settings, attr) is None]
|
423
291
|
if missing:
|
424
292
|
raise ValueError(
|
425
293
|
f"Missing required database connection settings: {', '.join(missing)}"
|
426
294
|
)
|
427
295
|
|
428
|
-
# We only need SQLAlchemy here if we're parsing a remote database connection
|
429
|
-
# string. Import it here so that we don't require the prefect-client package
|
430
|
-
# to have SQLAlchemy installed.
|
431
296
|
from sqlalchemy import URL
|
432
297
|
|
433
298
|
return URL(
|
434
|
-
drivername=
|
435
|
-
host=
|
436
|
-
port=
|
437
|
-
username=
|
438
|
-
password=
|
439
|
-
|
440
|
-
|
299
|
+
drivername=settings.api_database_driver,
|
300
|
+
host=settings.api_database_host,
|
301
|
+
port=settings.api_database_port or 5432,
|
302
|
+
username=settings.api_database_user,
|
303
|
+
password=(
|
304
|
+
settings.api_database_password.get_secret_value()
|
305
|
+
if settings.api_database_password
|
306
|
+
else None
|
307
|
+
),
|
308
|
+
database=settings.api_database_name,
|
309
|
+
query=[], # type: ignore
|
441
310
|
).render_as_string(hide_password=False)
|
442
311
|
|
443
|
-
elif
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
raise ValueError(f"Unsupported database driver: {driver}")
|
449
|
-
|
450
|
-
templater = template_with_settings(PREFECT_HOME, PREFECT_API_DATABASE_PASSWORD)
|
451
|
-
|
452
|
-
# If the user has provided a value, use it
|
453
|
-
if value is not None:
|
454
|
-
return templater(settings, value)
|
455
|
-
|
456
|
-
# Otherwise, the default is a database in a local file
|
457
|
-
home = PREFECT_HOME.value_from(settings)
|
458
|
-
|
459
|
-
old_default = home / "orion.db"
|
460
|
-
new_default = home / "prefect.db"
|
461
|
-
|
462
|
-
# If the old one exists and the new one does not, continue using the old one
|
463
|
-
if not new_default.exists():
|
464
|
-
if old_default.exists():
|
465
|
-
return "sqlite+aiosqlite:///" + str(old_default)
|
466
|
-
|
467
|
-
# Otherwise, return the new default
|
468
|
-
return "sqlite+aiosqlite:///" + str(new_default)
|
469
|
-
|
470
|
-
|
471
|
-
def default_ui_url(settings, value):
|
472
|
-
if value is not None:
|
473
|
-
return value
|
474
|
-
|
475
|
-
# Otherwise, infer a value from the API URL
|
476
|
-
ui_url = api_url = PREFECT_API_URL.value_from(settings)
|
477
|
-
|
478
|
-
if not api_url:
|
479
|
-
return None
|
480
|
-
|
481
|
-
cloud_url = PREFECT_CLOUD_API_URL.value_from(settings)
|
482
|
-
cloud_ui_url = PREFECT_CLOUD_UI_URL.value_from(settings)
|
483
|
-
if api_url.startswith(cloud_url):
|
484
|
-
ui_url = ui_url.replace(cloud_url, cloud_ui_url)
|
485
|
-
|
486
|
-
if ui_url.endswith("/api"):
|
487
|
-
# Handles open-source APIs
|
488
|
-
ui_url = ui_url[:-4]
|
489
|
-
|
490
|
-
# Handles Cloud APIs with content after `/api`
|
491
|
-
ui_url = ui_url.replace("/api/", "/")
|
492
|
-
|
493
|
-
# Update routing
|
494
|
-
ui_url = ui_url.replace("/accounts/", "/account/")
|
495
|
-
ui_url = ui_url.replace("/workspaces/", "/workspace/")
|
496
|
-
|
497
|
-
return ui_url
|
498
|
-
|
499
|
-
|
500
|
-
def default_cloud_ui_url(settings, value):
|
501
|
-
if value is not None:
|
502
|
-
return value
|
503
|
-
|
504
|
-
# Otherwise, infer a value from the API URL
|
505
|
-
ui_url = api_url = PREFECT_CLOUD_API_URL.value_from(settings)
|
506
|
-
|
507
|
-
if re.match(r"^https://api[\.\w]*.prefect.[^\.]+/", api_url):
|
508
|
-
ui_url = ui_url.replace("https://api", "https://app", 1)
|
509
|
-
|
510
|
-
if ui_url.endswith("/api"):
|
511
|
-
ui_url = ui_url[:-4]
|
512
|
-
|
513
|
-
return ui_url
|
514
|
-
|
515
|
-
|
516
|
-
# Setting definitions
|
517
|
-
|
518
|
-
PREFECT_HOME = Setting(
|
519
|
-
Path,
|
520
|
-
default=Path("~") / ".prefect",
|
521
|
-
value_callback=expanduser_in_path,
|
522
|
-
)
|
523
|
-
"""Prefect's home directory. Defaults to `~/.prefect`. This
|
524
|
-
directory may be created automatically when required.
|
525
|
-
"""
|
526
|
-
|
527
|
-
PREFECT_DEBUG_MODE = Setting(
|
528
|
-
bool,
|
529
|
-
default=False,
|
530
|
-
)
|
531
|
-
"""If `True`, places the API in debug mode. This may modify
|
532
|
-
behavior to facilitate debugging, including extra logs and other verbose
|
533
|
-
assistance. Defaults to `False`.
|
534
|
-
"""
|
535
|
-
|
536
|
-
PREFECT_CLI_COLORS = Setting(
|
537
|
-
bool,
|
538
|
-
default=True,
|
539
|
-
)
|
540
|
-
"""If `True`, use colors in CLI output. If `False`,
|
541
|
-
output will not include colors codes. Defaults to `True`.
|
542
|
-
"""
|
543
|
-
|
544
|
-
PREFECT_CLI_PROMPT = Setting(
|
545
|
-
Optional[bool],
|
546
|
-
default=None,
|
547
|
-
)
|
548
|
-
"""If `True`, use interactive prompts in CLI commands. If `False`, no interactive
|
549
|
-
prompts will be used. If `None`, the value will be dynamically determined based on
|
550
|
-
the presence of an interactive-enabled terminal.
|
551
|
-
"""
|
312
|
+
elif settings.api_database_driver == "sqlite+aiosqlite":
|
313
|
+
if settings.api_database_name:
|
314
|
+
value = f"{settings.api_database_driver}:///{settings.api_database_name}"
|
315
|
+
else:
|
316
|
+
value = f"sqlite+aiosqlite:///{settings.home}/prefect.db"
|
552
317
|
|
553
|
-
|
554
|
-
|
555
|
-
default=True,
|
556
|
-
)
|
557
|
-
"""If `True`, wrap text by inserting new lines in long lines
|
558
|
-
in CLI output. If `False`, output will not be wrapped. Defaults to `True`.
|
559
|
-
"""
|
318
|
+
elif settings.api_database_driver:
|
319
|
+
raise ValueError(f"Unsupported database driver: {settings.api_database_driver}")
|
560
320
|
|
561
|
-
|
562
|
-
|
563
|
-
default=False,
|
564
|
-
)
|
565
|
-
"""If `True`, places the API in test mode. This may modify
|
566
|
-
behavior to facilitate testing. Defaults to `False`.
|
567
|
-
"""
|
321
|
+
value = value if value else f"sqlite+aiosqlite:///{settings.home}/prefect.db"
|
322
|
+
return SecretStr(value)
|
568
323
|
|
569
|
-
PREFECT_UNIT_TEST_MODE = Setting(
|
570
|
-
bool,
|
571
|
-
default=False,
|
572
|
-
)
|
573
|
-
"""
|
574
|
-
This variable only exists to facilitate unit testing. If `True`,
|
575
|
-
code is executing in a unit test context. Defaults to `False`.
|
576
|
-
"""
|
577
|
-
PREFECT_UNIT_TEST_LOOP_DEBUG = Setting(
|
578
|
-
bool,
|
579
|
-
default=True,
|
580
|
-
)
|
581
|
-
"""
|
582
|
-
If `True` turns on debug mode for the unit testing event loop.
|
583
|
-
Defaults to `False`.
|
584
|
-
"""
|
585
324
|
|
586
|
-
|
587
|
-
|
588
|
-
default=None,
|
589
|
-
value_callback=only_return_value_in_test_mode,
|
590
|
-
)
|
591
|
-
"""
|
592
|
-
This variable only exists to facilitate testing of settings.
|
593
|
-
If accessed when `PREFECT_TEST_MODE` is not set, `None` is returned.
|
594
|
-
"""
|
325
|
+
###########################################################################
|
326
|
+
# Settings Loader
|
595
327
|
|
596
|
-
PREFECT_API_TLS_INSECURE_SKIP_VERIFY = Setting(
|
597
|
-
bool,
|
598
|
-
default=False,
|
599
|
-
)
|
600
|
-
"""If `True`, disables SSL checking to allow insecure requests.
|
601
|
-
This is recommended only during development, e.g. when using self-signed certificates.
|
602
|
-
"""
|
603
328
|
|
604
|
-
|
605
|
-
|
606
|
-
default=os.environ.get("SSL_CERT_FILE"),
|
607
|
-
)
|
608
|
-
"""
|
609
|
-
This configuration settings option specifies the path to an SSL certificate file.
|
610
|
-
When set, it allows the application to use the specified certificate for secure communication.
|
611
|
-
If left unset, the setting will default to the value provided by the `SSL_CERT_FILE` environment variable.
|
612
|
-
"""
|
329
|
+
def _get_profiles_path() -> Path:
|
330
|
+
"""Helper to get the profiles path"""
|
613
331
|
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
)
|
618
|
-
""
|
619
|
-
|
332
|
+
if is_test_mode():
|
333
|
+
return DEFAULT_PROFILES_PATH
|
334
|
+
if env_path := os.getenv("PREFECT_PROFILES_PATH"):
|
335
|
+
return Path(env_path)
|
336
|
+
if not (DEFAULT_PREFECT_HOME / "profiles.toml").exists():
|
337
|
+
return DEFAULT_PROFILES_PATH
|
338
|
+
return DEFAULT_PREFECT_HOME / "profiles.toml"
|
620
339
|
|
621
|
-
When using Prefect Cloud, this will include an account and workspace.
|
622
|
-
"""
|
623
340
|
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
)
|
628
|
-
"""If `True`, disable the warning when a user accidentally misconfigure its `PREFECT_API_URL`
|
629
|
-
Sometimes when a user manually set `PREFECT_API_URL` to a custom url,reverse-proxy for example,
|
630
|
-
we would like to silence this warning so we will set it to `FALSE`.
|
631
|
-
"""
|
632
|
-
|
633
|
-
PREFECT_API_KEY = Setting(
|
634
|
-
Optional[str],
|
635
|
-
default=None,
|
636
|
-
is_secret=True,
|
637
|
-
)
|
638
|
-
"""API key used to authenticate with a the Prefect API. Defaults to `None`."""
|
639
|
-
|
640
|
-
PREFECT_API_ENABLE_HTTP2 = Setting(bool, default=False)
|
641
|
-
"""
|
642
|
-
If true, enable support for HTTP/2 for communicating with an API.
|
643
|
-
|
644
|
-
If the API does not support HTTP/2, this will have no effect and connections will be
|
645
|
-
made via HTTP/1.1.
|
646
|
-
"""
|
341
|
+
class ProfileSettingsTomlLoader(PydanticBaseSettingsSource):
|
342
|
+
"""
|
343
|
+
Custom pydantic settings source to load profile settings from a toml file.
|
647
344
|
|
345
|
+
See https://docs.pydantic.dev/latest/concepts/pydantic_settings/#customise-settings-sources
|
346
|
+
"""
|
648
347
|
|
649
|
-
|
650
|
-
|
651
|
-
|
348
|
+
def __init__(self, settings_cls: Type[BaseSettings]):
|
349
|
+
super().__init__(settings_cls)
|
350
|
+
self.settings_cls = settings_cls
|
351
|
+
self.profiles_path = _get_profiles_path()
|
352
|
+
self.profile_settings = self._load_profile_settings()
|
652
353
|
|
653
|
-
|
654
|
-
|
354
|
+
def _load_profile_settings(self) -> Dict[str, Any]:
|
355
|
+
"""Helper method to load the profile settings from the profiles.toml file"""
|
655
356
|
|
656
|
-
|
657
|
-
|
658
|
-
"""
|
357
|
+
if not self.profiles_path.exists():
|
358
|
+
return {}
|
659
359
|
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
"""
|
360
|
+
try:
|
361
|
+
all_profile_data = toml.load(self.profiles_path)
|
362
|
+
except toml.TomlDecodeError:
|
363
|
+
warnings.warn(
|
364
|
+
f"Failed to load profiles from {self.profiles_path}. Please ensure the file is valid TOML."
|
365
|
+
)
|
366
|
+
return {}
|
668
367
|
|
368
|
+
if (
|
369
|
+
sys.argv[0].endswith("/prefect")
|
370
|
+
and len(sys.argv) >= 3
|
371
|
+
and sys.argv[1] == "--profile"
|
372
|
+
):
|
373
|
+
active_profile = sys.argv[2]
|
669
374
|
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
A comma-separated list of extra HTTP status codes to retry on. Defaults to an empty string.
|
675
|
-
429, 502 and 503 are always retried. Please note that not all routes are idempotent and retrying
|
676
|
-
may result in unexpected behavior.
|
677
|
-
"""
|
375
|
+
else:
|
376
|
+
active_profile = os.environ.get("PREFECT_PROFILE") or all_profile_data.get(
|
377
|
+
"active"
|
378
|
+
)
|
678
379
|
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
380
|
+
profiles_data = all_profile_data.get("profiles", {})
|
381
|
+
|
382
|
+
if not active_profile or active_profile not in profiles_data:
|
383
|
+
return {}
|
384
|
+
|
385
|
+
return profiles_data[active_profile]
|
386
|
+
|
387
|
+
def get_field_value(
|
388
|
+
self, field: FieldInfo, field_name: str
|
389
|
+
) -> Tuple[Any, str, bool]:
|
390
|
+
"""Concrete implementation to get the field value from the profile settings"""
|
391
|
+
value = self.profile_settings.get(f"PREFECT_{field_name.upper()}")
|
392
|
+
return value, field_name, self.field_is_complex(field)
|
393
|
+
|
394
|
+
def __call__(self) -> Dict[str, Any]:
|
395
|
+
"""Called by pydantic to get the settings from our custom source"""
|
396
|
+
if is_test_mode():
|
397
|
+
return {}
|
398
|
+
profile_settings: Dict[str, Any] = {}
|
399
|
+
for field_name, field in self.settings_cls.model_fields.items():
|
400
|
+
value, key, is_complex = self.get_field_value(field, field_name)
|
401
|
+
if value is not None:
|
402
|
+
prepared_value = self.prepare_field_value(
|
403
|
+
field_name, field, value, is_complex
|
404
|
+
)
|
405
|
+
profile_settings[key] = prepared_value
|
406
|
+
return profile_settings
|
683
407
|
|
684
|
-
When enabled (`True`), the client automatically manages CSRF tokens by
|
685
|
-
retrieving, storing, and including them in applicable state-changing requests
|
686
|
-
(POST, PUT, PATCH, DELETE) to the API.
|
687
408
|
|
688
|
-
|
689
|
-
|
409
|
+
###########################################################################
|
410
|
+
# Settings
|
690
411
|
|
691
|
-
Defaults to `True`, ensuring CSRF protection is enabled by default.
|
692
|
-
"""
|
693
412
|
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
)
|
698
|
-
"""API URL for Prefect Cloud. Used for authentication."""
|
413
|
+
class Settings(BaseSettings):
|
414
|
+
"""
|
415
|
+
Settings for Prefect using Pydantic settings.
|
699
416
|
|
417
|
+
See https://docs.pydantic.dev/latest/concepts/pydantic_settings
|
418
|
+
"""
|
700
419
|
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
)
|
706
|
-
"""
|
707
|
-
The URL for the UI. By default, this is inferred from the PREFECT_API_URL.
|
420
|
+
model_config = SettingsConfigDict(
|
421
|
+
env_file=".env",
|
422
|
+
env_prefix="PREFECT_",
|
423
|
+
extra="ignore",
|
424
|
+
)
|
708
425
|
|
709
|
-
|
710
|
-
|
711
|
-
|
426
|
+
@classmethod
|
427
|
+
def settings_customise_sources(
|
428
|
+
cls,
|
429
|
+
settings_cls: Type[BaseSettings],
|
430
|
+
init_settings: PydanticBaseSettingsSource,
|
431
|
+
env_settings: PydanticBaseSettingsSource,
|
432
|
+
dotenv_settings: PydanticBaseSettingsSource,
|
433
|
+
file_secret_settings: PydanticBaseSettingsSource,
|
434
|
+
) -> Tuple[PydanticBaseSettingsSource, ...]:
|
435
|
+
"""
|
436
|
+
Define an order for Prefect settings sources.
|
712
437
|
|
438
|
+
The order of the returned callables decides the priority of inputs; first item is the highest priority.
|
713
439
|
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
440
|
+
See https://docs.pydantic.dev/latest/concepts/pydantic_settings/#customise-settings-sources
|
441
|
+
"""
|
442
|
+
return (
|
443
|
+
init_settings,
|
444
|
+
env_settings,
|
445
|
+
dotenv_settings,
|
446
|
+
file_secret_settings,
|
447
|
+
ProfileSettingsTomlLoader(settings_cls),
|
448
|
+
)
|
721
449
|
|
722
|
-
|
723
|
-
|
724
|
-
"""
|
450
|
+
###########################################################################
|
451
|
+
# CLI
|
725
452
|
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
)
|
730
|
-
"""The default timeout for requests to the API"""
|
453
|
+
cli_colors: bool = Field(
|
454
|
+
default=True,
|
455
|
+
description="If True, use colors in CLI output. If `False`, output will not include colors codes.",
|
456
|
+
)
|
731
457
|
|
732
|
-
|
733
|
-
|
734
|
-
If
|
735
|
-
|
458
|
+
cli_prompt: Optional[bool] = Field(
|
459
|
+
default=None,
|
460
|
+
description="If `True`, use interactive prompts in CLI commands. If `False`, no interactive prompts will be used. If `None`, the value will be dynamically determined based on the presence of an interactive-enabled terminal.",
|
461
|
+
)
|
736
462
|
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
)
|
742
|
-
"""The path to a profiles configuration files."""
|
463
|
+
cli_wrap_lines: bool = Field(
|
464
|
+
default=True,
|
465
|
+
description="If `True`, wrap text by inserting new lines in long lines in CLI output. If `False`, output will not be wrapped.",
|
466
|
+
)
|
743
467
|
|
744
|
-
|
745
|
-
|
746
|
-
default="pickle",
|
747
|
-
)
|
748
|
-
"""The default serializer to use when not otherwise specified."""
|
468
|
+
###########################################################################
|
469
|
+
# Testing
|
749
470
|
|
471
|
+
test_mode: bool = Field(
|
472
|
+
default=False,
|
473
|
+
description="If `True`, places the API in test mode. This may modify behavior to facilitate testing.",
|
474
|
+
)
|
750
475
|
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
)
|
755
|
-
"""
|
756
|
-
The default setting for persisting results when not otherwise specified. If enabled,
|
757
|
-
flow and task results will be persisted unless they opt out.
|
758
|
-
"""
|
476
|
+
unit_test_mode: bool = Field(
|
477
|
+
default=False,
|
478
|
+
description="This setting only exists to facilitate unit testing. If `True`, code is executing in a unit test context. Defaults to `False`.",
|
479
|
+
)
|
759
480
|
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
)
|
764
|
-
"""
|
765
|
-
If `True`, enables a refresh of cached results: re-executing the
|
766
|
-
task will refresh the cached results. Defaults to `False`.
|
767
|
-
"""
|
481
|
+
unit_test_loop_debug: bool = Field(
|
482
|
+
default=True,
|
483
|
+
description="If `True` turns on debug mode for the unit testing event loop.",
|
484
|
+
)
|
768
485
|
|
769
|
-
|
770
|
-
""
|
771
|
-
This
|
772
|
-
|
773
|
-
"""
|
486
|
+
test_setting: Optional[Any] = Field(
|
487
|
+
default="FOO",
|
488
|
+
description="This setting only exists to facilitate unit testing. If in test mode, this setting will return its value. Otherwise, it returns `None`.",
|
489
|
+
)
|
774
490
|
|
775
|
-
|
776
|
-
|
777
|
-
This value sets the default number of retries for all flows.
|
778
|
-
This value does not overwrite individually set retries values on a flow
|
779
|
-
"""
|
491
|
+
###########################################################################
|
492
|
+
# Results settings
|
780
493
|
|
781
|
-
|
782
|
-
""
|
783
|
-
|
784
|
-
|
785
|
-
"""
|
494
|
+
results_default_serializer: str = Field(
|
495
|
+
default="pickle",
|
496
|
+
description="The default serializer to use when not otherwise specified.",
|
497
|
+
)
|
786
498
|
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
This value sets the default retry delay seconds for all tasks.
|
792
|
-
This value does not overwrite individually set retry delay seconds
|
793
|
-
"""
|
499
|
+
results_persist_by_default: bool = Field(
|
500
|
+
default=False,
|
501
|
+
description="The default setting for persisting results when not otherwise specified.",
|
502
|
+
)
|
794
503
|
|
795
|
-
|
796
|
-
|
797
|
-
The number of seconds to wait before retrying when a task run
|
798
|
-
cannot secure a concurrency slot from the server.
|
799
|
-
"""
|
504
|
+
###########################################################################
|
505
|
+
# API settings
|
800
506
|
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
507
|
+
api_url: Optional[str] = Field(
|
508
|
+
default=None,
|
509
|
+
description="The URL of the Prefect API. If not set, the client will attempt to infer it.",
|
510
|
+
)
|
511
|
+
api_key: Optional[SecretStr] = Field(
|
512
|
+
default=None,
|
513
|
+
description="The API key used for authentication with the Prefect API. Should be kept secret.",
|
514
|
+
)
|
807
515
|
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
)
|
813
|
-
"""The path to the memo store file."""
|
516
|
+
api_tls_insecure_skip_verify: bool = Field(
|
517
|
+
default=False,
|
518
|
+
description="If `True`, disables SSL checking to allow insecure requests. This is recommended only during development, e.g. when using self-signed certificates.",
|
519
|
+
)
|
814
520
|
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
)
|
819
|
-
"""
|
820
|
-
Controls whether or not block auto-registration on start
|
821
|
-
up should be memoized. Setting to False may result in slower server start
|
822
|
-
up times.
|
823
|
-
"""
|
521
|
+
api_ssl_cert_file: Optional[str] = Field(
|
522
|
+
default=os.environ.get("SSL_CERT_FILE"),
|
523
|
+
description="This configuration settings option specifies the path to an SSL certificate file.",
|
524
|
+
)
|
824
525
|
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
)
|
830
|
-
"""
|
831
|
-
The default logging level for Prefect loggers. Defaults to
|
832
|
-
"INFO" during normal operation. Is forced to "DEBUG" during debug mode.
|
833
|
-
"""
|
526
|
+
api_enable_http2: bool = Field(
|
527
|
+
default=False,
|
528
|
+
description="If true, enable support for HTTP/2 for communicating with an API. If the API does not support HTTP/2, this will have no effect and connections will be made via HTTP/1.1.",
|
529
|
+
)
|
834
530
|
|
531
|
+
api_request_timeout: float = Field(
|
532
|
+
default=60.0,
|
533
|
+
description="The default timeout for requests to the API",
|
534
|
+
)
|
835
535
|
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
)
|
841
|
-
"""
|
842
|
-
The default logging level for Prefect's internal machinery loggers. Defaults to
|
843
|
-
"ERROR" during normal operation. Is forced to "DEBUG" during debug mode.
|
844
|
-
"""
|
536
|
+
api_blocks_register_on_start: bool = Field(
|
537
|
+
default=True,
|
538
|
+
description="If set, any block types that have been imported will be registered with the backend on application startup. If not set, block types must be manually registered.",
|
539
|
+
)
|
845
540
|
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
)
|
850
|
-
"""The default logging level for the Prefect API server."""
|
541
|
+
api_log_retryable_errors: bool = Field(
|
542
|
+
default=False,
|
543
|
+
description="If `True`, log retryable errors in the API and it's services.",
|
544
|
+
)
|
851
545
|
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
)
|
857
|
-
"""
|
858
|
-
The path to a custom YAML logging configuration file. If
|
859
|
-
no file is found, the default `logging.yml` is used.
|
860
|
-
Defaults to a logging.yml in the Prefect home directory.
|
861
|
-
"""
|
546
|
+
api_default_limit: int = Field(
|
547
|
+
default=200,
|
548
|
+
description="The default limit applied to queries that can return multiple objects, such as `POST /flow_runs/filter`.",
|
549
|
+
)
|
862
550
|
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
)
|
868
|
-
"""
|
869
|
-
Additional loggers to attach to Prefect logging at runtime.
|
870
|
-
Values should be comma separated. The handlers attached to the 'prefect' logger
|
871
|
-
will be added to these loggers. Additionally, if the level is not set, it will
|
872
|
-
be set to the same level as the 'prefect' logger.
|
873
|
-
"""
|
551
|
+
api_task_cache_key_max_length: int = Field(
|
552
|
+
default=2000,
|
553
|
+
description="The maximum number of characters allowed for a task run cache key.",
|
554
|
+
)
|
874
555
|
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
)
|
879
|
-
"""
|
880
|
-
If set, `print` statements in flows and tasks will be redirected to the Prefect logger
|
881
|
-
for the given run. This setting can be overridden by individual tasks and flows.
|
882
|
-
"""
|
556
|
+
api_max_flow_run_graph_nodes: int = Field(
|
557
|
+
default=10000,
|
558
|
+
description="The maximum size of a flow run graph on the v2 API",
|
559
|
+
)
|
883
560
|
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
)
|
888
|
-
"""
|
889
|
-
Toggles sending logs to the API.
|
890
|
-
If `False`, logs sent to the API log handler will not be sent to the API.
|
891
|
-
"""
|
561
|
+
api_max_flow_run_graph_artifacts: int = Field(
|
562
|
+
default=10000,
|
563
|
+
description="The maximum number of artifacts to show on a flow run graph on the v2 API",
|
564
|
+
)
|
892
565
|
|
893
|
-
|
894
|
-
|
566
|
+
###########################################################################
|
567
|
+
# API Database settings
|
568
|
+
|
569
|
+
api_database_connection_url: Optional[SecretStr] = Field(
|
570
|
+
default=None,
|
571
|
+
description="""
|
572
|
+
A database connection URL in a SQLAlchemy-compatible
|
573
|
+
format. Prefect currently supports SQLite and Postgres. Note that all
|
574
|
+
Prefect database engines must use an async driver - for SQLite, use
|
575
|
+
`sqlite+aiosqlite` and for Postgres use `postgresql+asyncpg`.
|
576
|
+
|
577
|
+
SQLite in-memory databases can be used by providing the url
|
578
|
+
`sqlite+aiosqlite:///file::memory:?cache=shared&uri=true&check_same_thread=false`,
|
579
|
+
which will allow the database to be accessed by multiple threads. Note
|
580
|
+
that in-memory databases can not be accessed from multiple processes and
|
581
|
+
should only be used for simple tests.
|
582
|
+
""",
|
583
|
+
)
|
895
584
|
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
585
|
+
api_database_driver: Optional[
|
586
|
+
Literal["postgresql+asyncpg", "sqlite+aiosqlite"]
|
587
|
+
] = Field(
|
588
|
+
default=None,
|
589
|
+
description=(
|
590
|
+
"The database driver to use when connecting to the database. "
|
591
|
+
"If not set, the driver will be inferred from the connection URL."
|
592
|
+
),
|
593
|
+
)
|
901
594
|
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
)
|
906
|
-
"""The maximum size in bytes for a single log."""
|
595
|
+
api_database_host: Optional[str] = Field(
|
596
|
+
default=None,
|
597
|
+
description="The database server host.",
|
598
|
+
)
|
907
599
|
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
)
|
912
|
-
"""
|
913
|
-
Controls the behavior when loggers attempt to send logs to the API handler from outside
|
914
|
-
of a flow.
|
600
|
+
api_database_port: Optional[int] = Field(
|
601
|
+
default=None,
|
602
|
+
description="The database server port.",
|
603
|
+
)
|
915
604
|
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
605
|
+
api_database_user: Optional[str] = Field(
|
606
|
+
default=None,
|
607
|
+
description="The user to use when connecting to the database.",
|
608
|
+
)
|
920
609
|
|
921
|
-
|
610
|
+
api_database_name: Optional[str] = Field(
|
611
|
+
default=None,
|
612
|
+
description="The name of the Prefect database on the remote server, or the path to the database file for SQLite.",
|
613
|
+
)
|
922
614
|
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
615
|
+
api_database_password: Optional[SecretStr] = Field(
|
616
|
+
default=None,
|
617
|
+
description="The password to use when connecting to the database. Should be kept secret.",
|
618
|
+
)
|
927
619
|
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
)
|
932
|
-
"""
|
933
|
-
Controls connection pool size when using a PostgreSQL database with the Prefect API. If not set, the default SQLAlchemy pool size will be used.
|
934
|
-
"""
|
620
|
+
api_database_echo: bool = Field(
|
621
|
+
default=False,
|
622
|
+
description="If `True`, SQLAlchemy will log all SQL issued to the database. Defaults to `False`.",
|
623
|
+
)
|
935
624
|
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
)
|
940
|
-
"""
|
941
|
-
Controls maximum overflow of the connection pool when using a PostgreSQL database with the Prefect API. If not set, the default SQLAlchemy maximum overflow value will be used.
|
942
|
-
"""
|
625
|
+
api_database_migrate_on_start: bool = Field(
|
626
|
+
default=True,
|
627
|
+
description="If `True`, the database will be migrated on application startup.",
|
628
|
+
)
|
943
629
|
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
)
|
948
|
-
"""Whether to style console logs with color."""
|
630
|
+
api_database_timeout: Optional[float] = Field(
|
631
|
+
default=10.0,
|
632
|
+
description="A statement timeout, in seconds, applied to all database interactions made by the API. Defaults to 10 seconds.",
|
633
|
+
)
|
949
634
|
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
)
|
954
|
-
"""
|
955
|
-
Whether to interpret strings wrapped in square brackets as a style.
|
956
|
-
This allows styles to be conveniently added to log messages, e.g.
|
957
|
-
`[red]This is a red message.[/red]`. However, the downside is,
|
958
|
-
if enabled, strings that contain square brackets may be inaccurately
|
959
|
-
interpreted and lead to incomplete output, e.g.
|
960
|
-
`DROP TABLE [dbo].[SomeTable];"` outputs `DROP TABLE .[SomeTable];`.
|
961
|
-
"""
|
635
|
+
api_database_connection_timeout: Optional[float] = Field(
|
636
|
+
default=5,
|
637
|
+
description="A connection timeout, in seconds, applied to database connections. Defaults to `5`.",
|
638
|
+
)
|
962
639
|
|
963
|
-
|
964
|
-
|
965
|
-
Determines whether `State.result()` fetches results automatically or not.
|
966
|
-
In Prefect 2.6.0, the `State.result()` method was updated to be async
|
967
|
-
to facilitate automatic retrieval of results from storage which means when
|
968
|
-
writing async code you must `await` the call. For backwards compatibility,
|
969
|
-
the result is not retrieved by default for async users. You may opt into this
|
970
|
-
per call by passing `fetch=True` or toggle this setting to change the behavior
|
971
|
-
globally.
|
972
|
-
This setting does not affect users writing synchronous tasks and flows.
|
973
|
-
This setting does not affect retrieval of results when using `Future.result()`.
|
974
|
-
"""
|
640
|
+
###########################################################################
|
641
|
+
# API Services settings
|
975
642
|
|
643
|
+
api_services_scheduler_enabled: bool = Field(
|
644
|
+
default=True,
|
645
|
+
description="Whether or not to start the scheduler service in the server application.",
|
646
|
+
)
|
976
647
|
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
648
|
+
api_services_scheduler_loop_seconds: float = Field(
|
649
|
+
default=60,
|
650
|
+
description="""
|
651
|
+
The scheduler loop interval, in seconds. This determines
|
652
|
+
how often the scheduler will attempt to schedule new flow runs, but has no
|
653
|
+
impact on how quickly either flow runs or task runs are actually executed.
|
654
|
+
Defaults to `60`.
|
655
|
+
""",
|
656
|
+
)
|
986
657
|
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
`sqlite+aiosqlite` and for Postgres use `postgresql+asyncpg`.
|
998
|
-
|
999
|
-
SQLite in-memory databases can be used by providing the url
|
1000
|
-
`sqlite+aiosqlite:///file::memory:?cache=shared&uri=true&check_same_thread=false`,
|
1001
|
-
which will allow the database to be accessed by multiple threads. Note
|
1002
|
-
that in-memory databases can not be accessed from multiple processes and
|
1003
|
-
should only be used for simple tests.
|
1004
|
-
|
1005
|
-
Defaults to a sqlite database stored in the Prefect home directory.
|
1006
|
-
|
1007
|
-
If you need to provide password via a different environment variable, you use
|
1008
|
-
the `PREFECT_API_DATABASE_PASSWORD` setting. For example:
|
1009
|
-
|
1010
|
-
```
|
1011
|
-
PREFECT_API_DATABASE_PASSWORD='mypassword'
|
1012
|
-
PREFECT_API_DATABASE_CONNECTION_URL='postgresql+asyncpg://postgres:${PREFECT_API_DATABASE_PASSWORD}@localhost/prefect'
|
1013
|
-
```
|
1014
|
-
"""
|
658
|
+
api_services_scheduler_deployment_batch_size: int = Field(
|
659
|
+
default=100,
|
660
|
+
description="""
|
661
|
+
The number of deployments the scheduler will attempt to
|
662
|
+
schedule in a single batch. If there are more deployments than the batch
|
663
|
+
size, the scheduler immediately attempts to schedule the next batch; it
|
664
|
+
does not sleep for `scheduler_loop_seconds` until it has visited every
|
665
|
+
deployment once. Defaults to `100`.
|
666
|
+
""",
|
667
|
+
)
|
1015
668
|
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
669
|
+
api_services_scheduler_max_runs: int = Field(
|
670
|
+
default=100,
|
671
|
+
description="""
|
672
|
+
The scheduler will attempt to schedule up to this many
|
673
|
+
auto-scheduled runs in the future. Note that runs may have fewer than
|
674
|
+
this many scheduled runs, depending on the value of
|
675
|
+
`scheduler_max_scheduled_time`. Defaults to `100`.
|
676
|
+
""",
|
677
|
+
)
|
1023
678
|
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
679
|
+
api_services_scheduler_min_runs: int = Field(
|
680
|
+
default=3,
|
681
|
+
description="""
|
682
|
+
The scheduler will attempt to schedule at least this many
|
683
|
+
auto-scheduled runs in the future. Note that runs may have more than
|
684
|
+
this many scheduled runs, depending on the value of
|
685
|
+
`scheduler_min_scheduled_time`. Defaults to `3`.
|
686
|
+
""",
|
687
|
+
)
|
1028
688
|
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
689
|
+
api_services_scheduler_max_scheduled_time: timedelta = Field(
|
690
|
+
default=timedelta(days=100),
|
691
|
+
description="""
|
692
|
+
The scheduler will create new runs up to this far in the
|
693
|
+
future. Note that this setting will take precedence over
|
694
|
+
`scheduler_max_runs`: if a flow runs once a month and
|
695
|
+
`scheduler_max_scheduled_time` is three months, then only three runs will be
|
696
|
+
scheduled. Defaults to 100 days (`8640000` seconds).
|
697
|
+
""",
|
698
|
+
)
|
1033
699
|
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
700
|
+
api_services_scheduler_min_scheduled_time: timedelta = Field(
|
701
|
+
default=timedelta(hours=1),
|
702
|
+
description="""
|
703
|
+
The scheduler will create new runs at least this far in the
|
704
|
+
future. Note that this setting will take precedence over `scheduler_min_runs`:
|
705
|
+
if a flow runs every hour and `scheduler_min_scheduled_time` is three hours,
|
706
|
+
then three runs will be scheduled even if `scheduler_min_runs` is 1. Defaults to
|
707
|
+
""",
|
708
|
+
)
|
1038
709
|
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
710
|
+
api_services_scheduler_insert_batch_size: int = Field(
|
711
|
+
default=500,
|
712
|
+
description="""
|
713
|
+
The number of runs the scheduler will attempt to insert in a single batch.
|
714
|
+
Defaults to `500`.
|
715
|
+
""",
|
716
|
+
)
|
1044
717
|
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
)
|
1050
|
-
"""
|
1051
|
-
Password to template into the `PREFECT_API_DATABASE_CONNECTION_URL`.
|
1052
|
-
This is useful if the password must be provided separately from the connection URL.
|
1053
|
-
To use this setting, you must include it in your connection URL.
|
1054
|
-
"""
|
718
|
+
api_services_late_runs_enabled: bool = Field(
|
719
|
+
default=True,
|
720
|
+
description="Whether or not to start the late runs service in the server application.",
|
721
|
+
)
|
1055
722
|
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
"""
|
723
|
+
api_services_late_runs_loop_seconds: float = Field(
|
724
|
+
default=5,
|
725
|
+
description="""
|
726
|
+
The late runs service will look for runs to mark as late this often. Defaults to `5`.
|
727
|
+
""",
|
728
|
+
)
|
1061
729
|
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
"""
|
730
|
+
api_services_late_runs_after_seconds: timedelta = Field(
|
731
|
+
default=timedelta(seconds=15),
|
732
|
+
description="""
|
733
|
+
The late runs service will mark runs as late after they have exceeded their scheduled start time by this many seconds. Defaults to `5` seconds.
|
734
|
+
""",
|
735
|
+
)
|
1067
736
|
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
"""
|
1073
|
-
|
1074
|
-
Defaults to 10 seconds.
|
1075
|
-
"""
|
737
|
+
api_services_pause_expirations_loop_seconds: float = Field(
|
738
|
+
default=5,
|
739
|
+
description="""
|
740
|
+
The pause expiration service will look for runs to mark as failed this often. Defaults to `5`.
|
741
|
+
""",
|
742
|
+
)
|
1076
743
|
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
)
|
1081
|
-
"""A connection timeout, in seconds, applied to database
|
1082
|
-
connections. Defaults to `5`.
|
1083
|
-
"""
|
744
|
+
api_services_cancellation_cleanup_enabled: bool = Field(
|
745
|
+
default=True,
|
746
|
+
description="Whether or not to start the cancellation cleanup service in the server application.",
|
747
|
+
)
|
1084
748
|
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
"""
|
1090
|
-
|
1091
|
-
impact on how quickly either flow runs or task runs are actually executed.
|
1092
|
-
Defaults to `60`.
|
1093
|
-
"""
|
749
|
+
api_services_cancellation_cleanup_loop_seconds: float = Field(
|
750
|
+
default=20,
|
751
|
+
description="""
|
752
|
+
The cancellation cleanup service will look non-terminal tasks and subflows this often. Defaults to `20`.
|
753
|
+
""",
|
754
|
+
)
|
1094
755
|
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
)
|
1099
|
-
"""The number of deployments the scheduler will attempt to
|
1100
|
-
schedule in a single batch. If there are more deployments than the batch
|
1101
|
-
size, the scheduler immediately attempts to schedule the next batch; it
|
1102
|
-
does not sleep for `scheduler_loop_seconds` until it has visited every
|
1103
|
-
deployment once. Defaults to `100`.
|
1104
|
-
"""
|
756
|
+
api_services_foreman_enabled: bool = Field(
|
757
|
+
default=True,
|
758
|
+
description="Whether or not to start the Foreman service in the server application.",
|
759
|
+
)
|
1105
760
|
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
`scheduler_max_scheduled_time`. Defaults to `100`.
|
1114
|
-
"""
|
761
|
+
api_services_foreman_loop_seconds: float = Field(
|
762
|
+
default=15,
|
763
|
+
description="""
|
764
|
+
The number of seconds to wait between each iteration of the Foreman loop which checks
|
765
|
+
for offline workers and updates work pool status. Defaults to `15`.
|
766
|
+
""",
|
767
|
+
)
|
1115
768
|
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
"""
|
1121
|
-
|
1122
|
-
this many scheduled runs, depending on the value of
|
1123
|
-
`scheduler_min_scheduled_time`. Defaults to `3`.
|
1124
|
-
"""
|
769
|
+
api_services_foreman_inactivity_heartbeat_multiple: int = Field(
|
770
|
+
default=3,
|
771
|
+
description="""
|
772
|
+
The number of heartbeats that must be missed before a worker is marked as offline. Defaults to `3`.
|
773
|
+
""",
|
774
|
+
)
|
1125
775
|
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
`scheduler_max_scheduled_time` is three months, then only three runs will be
|
1134
|
-
scheduled. Defaults to 100 days (`8640000` seconds).
|
1135
|
-
"""
|
776
|
+
api_services_foreman_fallback_heartbeat_interval_seconds: int = Field(
|
777
|
+
default=30,
|
778
|
+
description="""
|
779
|
+
The number of seconds to use for online/offline evaluation if a worker's heartbeat
|
780
|
+
interval is not set. Defaults to `30`.
|
781
|
+
""",
|
782
|
+
)
|
1136
783
|
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
then three runs will be scheduled even if `scheduler_min_runs` is 1. Defaults to
|
1145
|
-
1 hour (`3600` seconds).
|
1146
|
-
"""
|
784
|
+
api_services_foreman_deployment_last_polled_timeout_seconds: int = Field(
|
785
|
+
default=60,
|
786
|
+
description="""
|
787
|
+
The number of seconds before a deployment is marked as not ready if it has not been
|
788
|
+
polled. Defaults to `60`.
|
789
|
+
""",
|
790
|
+
)
|
1147
791
|
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
Defaults to `500`.
|
1156
|
-
"""
|
792
|
+
api_services_foreman_work_queue_last_polled_timeout_seconds: int = Field(
|
793
|
+
default=60,
|
794
|
+
description="""
|
795
|
+
The number of seconds before a work queue is marked as not ready if it has not been
|
796
|
+
polled. Defaults to `60`.
|
797
|
+
""",
|
798
|
+
)
|
1157
799
|
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
)
|
1162
|
-
"""The late runs service will look for runs to mark as late
|
1163
|
-
this often. Defaults to `5`.
|
1164
|
-
"""
|
800
|
+
api_services_task_run_recorder_enabled: bool = Field(
|
801
|
+
default=True,
|
802
|
+
description="Whether or not to start the task run recorder service in the server application.",
|
803
|
+
)
|
1165
804
|
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
"""
|
805
|
+
api_services_flow_run_notifications_enabled: bool = Field(
|
806
|
+
default=True,
|
807
|
+
description="""
|
808
|
+
Whether or not to start the flow run notifications service in the server application.
|
809
|
+
If disabled, you will need to run this service separately to send flow run notifications.
|
810
|
+
""",
|
811
|
+
)
|
1174
812
|
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
"""
|
813
|
+
api_services_pause_expirations_enabled: bool = Field(
|
814
|
+
default=True,
|
815
|
+
description="""
|
816
|
+
Whether or not to start the paused flow run expiration service in the server
|
817
|
+
application. If disabled, paused flows that have timed out will remain in a Paused state
|
818
|
+
until a resume attempt.
|
819
|
+
""",
|
820
|
+
)
|
1182
821
|
|
1183
|
-
|
1184
|
-
|
1185
|
-
default=20,
|
1186
|
-
)
|
1187
|
-
"""The cancellation cleanup service will look non-terminal tasks and subflows
|
1188
|
-
this often. Defaults to `20`.
|
1189
|
-
"""
|
822
|
+
###########################################################################
|
823
|
+
# Cloud settings
|
1190
824
|
|
1191
|
-
|
1192
|
-
"
|
825
|
+
cloud_api_url: str = Field(
|
826
|
+
default="https://api.prefect.cloud/api",
|
827
|
+
description="API URL for Prefect Cloud. Used for authentication.",
|
828
|
+
)
|
1193
829
|
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
830
|
+
cloud_ui_url: Optional[str] = Field(
|
831
|
+
default=None,
|
832
|
+
description="The URL of the Prefect Cloud UI. If not set, the client will attempt to infer it.",
|
833
|
+
)
|
1197
834
|
|
835
|
+
###########################################################################
|
836
|
+
# Logging settings
|
1198
837
|
|
1199
|
-
|
1200
|
-
"
|
838
|
+
logging_level: LogLevel = Field(
|
839
|
+
default="INFO",
|
840
|
+
description="The default logging level for Prefect loggers.",
|
841
|
+
)
|
1201
842
|
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
interval is not set."""
|
843
|
+
logging_internal_level: LogLevel = Field(
|
844
|
+
default="ERROR",
|
845
|
+
description="The default logging level for Prefect's internal machinery loggers.",
|
846
|
+
)
|
1207
847
|
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
polled."""
|
848
|
+
logging_server_level: LogLevel = Field(
|
849
|
+
default="WARNING",
|
850
|
+
description="The default logging level for the Prefect API server.",
|
851
|
+
)
|
1213
852
|
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
polled."""
|
853
|
+
logging_settings_path: Optional[Path] = Field(
|
854
|
+
default=None,
|
855
|
+
description="The path to a custom YAML logging configuration file.",
|
856
|
+
)
|
1219
857
|
|
1220
|
-
|
1221
|
-
|
858
|
+
logging_extra_loggers: Annotated[
|
859
|
+
Union[str, list[str], None],
|
860
|
+
AfterValidator(lambda v: [n.strip() for n in v.split(",")] if v else []),
|
861
|
+
] = Field(
|
862
|
+
default=None,
|
863
|
+
description="Additional loggers to attach to Prefect logging at runtime.",
|
864
|
+
)
|
1222
865
|
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
866
|
+
logging_log_prints: bool = Field(
|
867
|
+
default=False,
|
868
|
+
description="If `True`, `print` statements in flows and tasks will be redirected to the Prefect logger for the given run.",
|
869
|
+
)
|
1227
870
|
|
871
|
+
logging_to_api_enabled: bool = Field(
|
872
|
+
default=True,
|
873
|
+
description="If `True`, logs will be sent to the API.",
|
874
|
+
)
|
1228
875
|
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
)
|
1233
|
-
"""The default limit applied to queries that can return
|
1234
|
-
multiple objects, such as `POST /flow_runs/filter`.
|
1235
|
-
"""
|
876
|
+
logging_to_api_batch_interval: float = Field(
|
877
|
+
default=2.0,
|
878
|
+
description="The number of seconds between batched writes of logs to the API.",
|
879
|
+
)
|
1236
880
|
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
)
|
1241
|
-
"""The API's host address (defaults to `127.0.0.1`)."""
|
881
|
+
logging_to_api_batch_size: int = Field(
|
882
|
+
default=4_000_000,
|
883
|
+
description="The number of logs to batch before sending to the API.",
|
884
|
+
)
|
1242
885
|
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
)
|
1247
|
-
"""The API's port address (defaults to `4200`)."""
|
886
|
+
logging_to_api_max_log_size: int = Field(
|
887
|
+
default=1_000_000,
|
888
|
+
description="The maximum size in bytes for a single log.",
|
889
|
+
)
|
1248
890
|
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
891
|
+
logging_to_api_when_missing_flow: Literal["warn", "error", "ignore"] = Field(
|
892
|
+
default="warn",
|
893
|
+
description="""
|
894
|
+
Controls the behavior when loggers attempt to send logs to the API handler from outside of a flow.
|
895
|
+
|
896
|
+
All logs sent to the API must be associated with a flow run. The API log handler can
|
897
|
+
only be used outside of a flow by manually providing a flow run identifier. Logs
|
898
|
+
that are not associated with a flow run will not be sent to the API. This setting can
|
899
|
+
be used to determine if a warning or error is displayed when the identifier is missing.
|
900
|
+
|
901
|
+
The following options are available:
|
902
|
+
|
903
|
+
- "warn": Log a warning message.
|
904
|
+
- "error": Raise an error.
|
905
|
+
- "ignore": Do not log a warning message or raise an error.
|
906
|
+
""",
|
907
|
+
)
|
1256
908
|
|
1257
|
-
|
1258
|
-
|
909
|
+
logging_colors: bool = Field(
|
910
|
+
default=True,
|
911
|
+
description="If `True`, use colors in CLI output. If `False`, output will not include colors codes.",
|
912
|
+
)
|
1259
913
|
|
1260
|
-
|
1261
|
-
|
1262
|
-
"""
|
914
|
+
logging_markup: bool = Field(
|
915
|
+
default=False,
|
916
|
+
description="""
|
917
|
+
Whether to interpret strings wrapped in square brackets as a style.
|
918
|
+
This allows styles to be conveniently added to log messages, e.g.
|
919
|
+
`[red]This is a red message.[/red]`. However, the downside is, if enabled,
|
920
|
+
strings that contain square brackets may be inaccurately interpreted and
|
921
|
+
lead to incomplete output, e.g.
|
922
|
+
`[red]This is a red message.[/red]` may be interpreted as
|
923
|
+
`[red]This is a red message.[/red]`.
|
924
|
+
""",
|
925
|
+
)
|
1263
926
|
|
1264
|
-
|
1265
|
-
|
1266
|
-
Controls the activation of CSRF protection for the Prefect server API.
|
927
|
+
###########################################################################
|
928
|
+
# Server settings
|
1267
929
|
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
behalf of authenticated users.
|
930
|
+
server_api_host: str = Field(
|
931
|
+
default="127.0.0.1",
|
932
|
+
description="The API's host address (defaults to `127.0.0.1`).",
|
933
|
+
)
|
1273
934
|
|
1274
|
-
|
1275
|
-
|
935
|
+
server_api_port: int = Field(
|
936
|
+
default=4200,
|
937
|
+
description="The API's port address (defaults to `4200`).",
|
938
|
+
)
|
1276
939
|
|
1277
|
-
|
1278
|
-
|
1279
|
-
"""
|
940
|
+
server_api_keepalive_timeout: int = Field(
|
941
|
+
default=5,
|
942
|
+
description="""
|
943
|
+
The API's keep alive timeout (defaults to `5`).
|
944
|
+
Refer to https://www.uvicorn.org/settings/#timeouts for details.
|
1280
945
|
|
1281
|
-
|
1282
|
-
|
1283
|
-
Specifies the duration for which a CSRF token remains valid after being issued
|
1284
|
-
by the server.
|
946
|
+
When the API is hosted behind a load balancer, you may want to set this to a value
|
947
|
+
greater than the load balancer's idle timeout.
|
1285
948
|
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
949
|
+
Note this setting only applies when calling `prefect server start`; if hosting the
|
950
|
+
API with another tool you will need to configure this there instead.
|
951
|
+
""",
|
952
|
+
)
|
1290
953
|
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1295
|
-
"""
|
1296
|
-
A comma-separated list of origins that are authorized to make cross-origin requests to the API.
|
954
|
+
server_csrf_protection_enabled: bool = Field(
|
955
|
+
default=False,
|
956
|
+
description="""
|
957
|
+
Controls the activation of CSRF protection for the Prefect server API.
|
1297
958
|
|
1298
|
-
|
1299
|
-
|
959
|
+
When enabled (`True`), the server enforces CSRF validation checks on incoming
|
960
|
+
state-changing requests (POST, PUT, PATCH, DELETE), requiring a valid CSRF
|
961
|
+
token to be included in the request headers or body. This adds a layer of
|
962
|
+
security by preventing unauthorized or malicious sites from making requests on
|
963
|
+
behalf of authenticated users.
|
1300
964
|
|
1301
|
-
|
1302
|
-
|
1303
|
-
default="*",
|
1304
|
-
)
|
1305
|
-
"""
|
1306
|
-
A comma-separated list of methods that are authorized to make cross-origin requests to the API.
|
965
|
+
It is recommended to enable this setting in production environments where the
|
966
|
+
API is exposed to web clients to safeguard against CSRF attacks.
|
1307
967
|
|
1308
|
-
|
1309
|
-
|
968
|
+
Note: Enabling this setting requires corresponding support in the client for
|
969
|
+
CSRF token management. See PREFECT_CLIENT_CSRF_SUPPORT_ENABLED for more.
|
970
|
+
""",
|
971
|
+
)
|
1310
972
|
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
A comma-separated list of headers that are authorized to make cross-origin requests to the API.
|
973
|
+
server_csrf_token_expiration: timedelta = Field(
|
974
|
+
default=timedelta(hours=1),
|
975
|
+
description="""
|
976
|
+
Specifies the duration for which a CSRF token remains valid after being issued
|
977
|
+
by the server.
|
1317
978
|
|
1318
|
-
|
1319
|
-
|
979
|
+
The default expiration time is set to 1 hour, which offers a reasonable
|
980
|
+
compromise. Adjust this setting based on your specific security requirements
|
981
|
+
and usage patterns.
|
982
|
+
""",
|
983
|
+
)
|
1320
984
|
|
1321
|
-
|
1322
|
-
""
|
1323
|
-
|
1324
|
-
|
985
|
+
server_cors_allowed_origins: str = Field(
|
986
|
+
default="*",
|
987
|
+
description="""
|
988
|
+
A comma-separated list of origins that are authorized to make cross-origin requests to the API.
|
1325
989
|
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1329
|
-
)
|
1330
|
-
"""
|
1331
|
-
The number of seconds to wait for an ephemeral server to respond on start up before erroring.
|
1332
|
-
"""
|
990
|
+
By default, this is set to `*`, which allows requests from all origins.
|
991
|
+
""",
|
992
|
+
)
|
1333
993
|
|
1334
|
-
|
1335
|
-
|
1336
|
-
|
1337
|
-
|
1338
|
-
"""Whether or not to serve the Prefect UI."""
|
994
|
+
server_cors_allowed_methods: str = Field(
|
995
|
+
default="*",
|
996
|
+
description="""
|
997
|
+
A comma-separated list of methods that are authorized to make cross-origin requests to the API.
|
1339
998
|
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
value_callback=default_ui_api_url,
|
1344
|
-
)
|
1345
|
-
"""The connection url for communication from the UI to the API.
|
1346
|
-
Defaults to `PREFECT_API_URL` if set. Otherwise, the default URL is generated from
|
1347
|
-
`PREFECT_SERVER_API_HOST` and `PREFECT_SERVER_API_PORT`. If providing a custom value,
|
1348
|
-
the aforementioned settings may be templated into the given string.
|
1349
|
-
"""
|
999
|
+
By default, this is set to `*`, which allows requests from all methods.
|
1000
|
+
""",
|
1001
|
+
)
|
1350
1002
|
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
"""
|
1356
|
-
When enabled, Prefect sends anonymous data (e.g. count of flow runs, package version)
|
1357
|
-
on server startup to help us improve our product.
|
1358
|
-
"""
|
1003
|
+
server_cors_allowed_headers: str = Field(
|
1004
|
+
default="*",
|
1005
|
+
description="""
|
1006
|
+
A comma-separated list of headers that are authorized to make cross-origin requests to the API.
|
1359
1007
|
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
)
|
1364
|
-
"""Whether or not to start the scheduling service in the server application.
|
1365
|
-
If disabled, you will need to run this service separately to schedule runs for deployments.
|
1366
|
-
"""
|
1008
|
+
By default, this is set to `*`, which allows requests from all headers.
|
1009
|
+
""",
|
1010
|
+
)
|
1367
1011
|
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
"""
|
1373
|
-
|
1374
|
-
scheduled start time marked as late.
|
1375
|
-
"""
|
1012
|
+
server_allow_ephemeral_mode: bool = Field(
|
1013
|
+
default=False,
|
1014
|
+
description="""
|
1015
|
+
Controls whether or not a subprocess server can be started when no API URL is provided.
|
1016
|
+
""",
|
1017
|
+
)
|
1376
1018
|
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1019
|
+
server_ephemeral_startup_timeout_seconds: int = Field(
|
1020
|
+
default=10,
|
1021
|
+
description="""
|
1022
|
+
The number of seconds to wait for the server to start when ephemeral mode is enabled.
|
1023
|
+
Defaults to `10`.
|
1024
|
+
""",
|
1025
|
+
)
|
1384
1026
|
|
1385
|
-
|
1386
|
-
|
1387
|
-
|
1388
|
-
)
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1392
|
-
"""
|
1027
|
+
server_analytics_enabled: bool = Field(
|
1028
|
+
default=True,
|
1029
|
+
description="""
|
1030
|
+
When enabled, Prefect sends anonymous data (e.g. count of flow runs, package version)
|
1031
|
+
on server startup to help us improve our product.
|
1032
|
+
""",
|
1033
|
+
)
|
1393
1034
|
|
1394
|
-
|
1395
|
-
|
1396
|
-
The maximum number of characters allowed for a task run cache key.
|
1397
|
-
This setting cannot be changed client-side, it must be set on the server.
|
1398
|
-
"""
|
1035
|
+
###########################################################################
|
1036
|
+
# UI settings
|
1399
1037
|
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1403
|
-
)
|
1404
|
-
"""Whether or not to start the cancellation cleanup service in the server
|
1405
|
-
application. If disabled, task runs and subflow runs belonging to cancelled flows may
|
1406
|
-
remain in non-terminal states.
|
1407
|
-
"""
|
1038
|
+
ui_enabled: bool = Field(
|
1039
|
+
default=True,
|
1040
|
+
description="Whether or not to serve the Prefect UI.",
|
1041
|
+
)
|
1408
1042
|
|
1409
|
-
|
1410
|
-
|
1411
|
-
The
|
1412
|
-
|
1043
|
+
ui_url: Optional[str] = Field(
|
1044
|
+
default=None,
|
1045
|
+
description="The URL of the Prefect UI. If not set, the client will attempt to infer it.",
|
1046
|
+
)
|
1413
1047
|
|
1414
|
-
|
1415
|
-
|
1416
|
-
The
|
1417
|
-
|
1048
|
+
ui_api_url: Optional[str] = Field(
|
1049
|
+
default=None,
|
1050
|
+
description="The connection url for communication from the UI to the API. Defaults to `PREFECT_API_URL` if set. Otherwise, the default URL is generated from `PREFECT_SERVER_API_HOST` and `PREFECT_SERVER_API_PORT`.",
|
1051
|
+
)
|
1418
1052
|
|
1419
|
-
|
1053
|
+
ui_serve_base: str = Field(
|
1054
|
+
default="/",
|
1055
|
+
description="The base URL path to serve the Prefect UI from.",
|
1056
|
+
)
|
1420
1057
|
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
1424
|
-
|
1058
|
+
ui_static_directory: Optional[str] = Field(
|
1059
|
+
default=None,
|
1060
|
+
description="The directory to serve static files from. This should be used when running into permissions issues when attempting to serve the UI from the default directory (for example when running in a Docker container).",
|
1061
|
+
)
|
1425
1062
|
|
1426
|
-
|
1427
|
-
|
1428
|
-
Number of seconds a runner should wait between queries for scheduled work.
|
1429
|
-
"""
|
1063
|
+
###########################################################################
|
1064
|
+
# Events settings
|
1430
1065
|
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1066
|
+
api_services_triggers_enabled: bool = Field(
|
1067
|
+
default=True,
|
1068
|
+
description="Whether or not to start the triggers service in the server application.",
|
1069
|
+
)
|
1435
1070
|
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1071
|
+
api_services_event_persister_enabled: bool = Field(
|
1072
|
+
default=True,
|
1073
|
+
description="Whether or not to start the event persister service in the server application.",
|
1074
|
+
)
|
1440
1075
|
|
1441
|
-
|
1442
|
-
|
1443
|
-
|
1444
|
-
""
|
1076
|
+
api_services_event_persister_batch_size: int = Field(
|
1077
|
+
default=20,
|
1078
|
+
gt=0,
|
1079
|
+
description="The number of events the event persister will attempt to insert in one batch.",
|
1080
|
+
)
|
1445
1081
|
|
1446
|
-
|
1447
|
-
|
1448
|
-
|
1449
|
-
""
|
1082
|
+
api_services_event_persister_flush_interval: float = Field(
|
1083
|
+
default=5,
|
1084
|
+
gt=0.0,
|
1085
|
+
description="The maximum number of seconds between flushes of the event persister.",
|
1086
|
+
)
|
1450
1087
|
|
1451
|
-
|
1452
|
-
|
1453
|
-
Whether or not to
|
1454
|
-
|
1088
|
+
api_events_stream_out_enabled: bool = Field(
|
1089
|
+
default=True,
|
1090
|
+
description="Whether or not to stream events out to the API via websockets.",
|
1091
|
+
)
|
1455
1092
|
|
1456
|
-
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1093
|
+
api_enable_metrics: bool = Field(
|
1094
|
+
default=False,
|
1095
|
+
description="Whether or not to enable Prometheus metrics in the API.",
|
1096
|
+
)
|
1460
1097
|
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1098
|
+
api_events_related_resource_cache_ttl: timedelta = Field(
|
1099
|
+
default=timedelta(minutes=5),
|
1100
|
+
description="The number of seconds to cache related resources for in the API.",
|
1101
|
+
)
|
1465
1102
|
|
1466
|
-
|
1467
|
-
|
1468
|
-
|
1469
|
-
|
1103
|
+
client_enable_metrics: bool = Field(
|
1104
|
+
default=False,
|
1105
|
+
description="Whether or not to enable Prometheus metrics in the client.",
|
1106
|
+
)
|
1470
1107
|
|
1471
|
-
|
1472
|
-
""
|
1473
|
-
|
1474
|
-
Can be used to compensate for infrastructure start up time for a worker.
|
1475
|
-
"""
|
1108
|
+
client_metrics_port: int = Field(
|
1109
|
+
default=4201, description="The port to expose the client Prometheus metrics on."
|
1110
|
+
)
|
1476
1111
|
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1480
|
-
)
|
1481
|
-
"""
|
1482
|
-
The host address the worker's webserver should bind to.
|
1483
|
-
"""
|
1112
|
+
events_maximum_labels_per_resource: int = Field(
|
1113
|
+
default=500,
|
1114
|
+
description="The maximum number of labels a resource may have.",
|
1115
|
+
)
|
1484
1116
|
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
)
|
1489
|
-
"""
|
1490
|
-
The port the worker's webserver should bind to.
|
1491
|
-
"""
|
1117
|
+
events_maximum_related_resources: int = Field(
|
1118
|
+
default=500,
|
1119
|
+
description="The maximum number of related resources an Event may have.",
|
1120
|
+
)
|
1492
1121
|
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1122
|
+
events_maximum_size_bytes: int = Field(
|
1123
|
+
default=1_500_000,
|
1124
|
+
description="The maximum size of an Event when serialized to JSON",
|
1125
|
+
)
|
1496
1126
|
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1500
|
-
)
|
1501
|
-
"""
|
1502
|
-
Whether or not to delete failed task submissions from the database.
|
1503
|
-
"""
|
1127
|
+
events_expired_bucket_buffer: timedelta = Field(
|
1128
|
+
default=timedelta(seconds=60),
|
1129
|
+
description="The amount of time to retain expired automation buckets",
|
1130
|
+
)
|
1504
1131
|
|
1505
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
)
|
1509
|
-
"""
|
1510
|
-
The maximum number of scheduled tasks to queue for submission.
|
1511
|
-
"""
|
1132
|
+
events_proactive_granularity: timedelta = Field(
|
1133
|
+
default=timedelta(seconds=5),
|
1134
|
+
description="How frequently proactive automations are evaluated",
|
1135
|
+
)
|
1512
1136
|
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
)
|
1517
|
-
"""
|
1518
|
-
The maximum number of retries to queue for submission.
|
1519
|
-
"""
|
1137
|
+
events_retention_period: timedelta = Field(
|
1138
|
+
default=timedelta(days=7),
|
1139
|
+
description="The amount of time to retain events in the database.",
|
1140
|
+
)
|
1520
1141
|
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
1524
|
-
)
|
1525
|
-
"""
|
1526
|
-
How long before a PENDING task are made available to another task worker. In practice,
|
1527
|
-
a task worker should move a task from PENDING to RUNNING very quickly, so runs stuck in
|
1528
|
-
PENDING for a while is a sign that the task worker may have crashed.
|
1529
|
-
"""
|
1142
|
+
events_maximum_websocket_backfill: timedelta = Field(
|
1143
|
+
default=timedelta(minutes=15),
|
1144
|
+
description="The maximum range to look back for backfilling events for a websocket subscriber.",
|
1145
|
+
)
|
1530
1146
|
|
1531
|
-
|
1147
|
+
events_websocket_backfill_page_size: int = Field(
|
1148
|
+
default=250,
|
1149
|
+
gt=0,
|
1150
|
+
description="The page size for the queries to backfill events for websocket subscribers.",
|
1151
|
+
)
|
1532
1152
|
|
1533
|
-
|
1153
|
+
###########################################################################
|
1154
|
+
# uncategorized
|
1534
1155
|
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1538
|
-
)
|
1539
|
-
|
1156
|
+
home: Annotated[Path, BeforeValidator(lambda x: Path(x).expanduser())] = Field(
|
1157
|
+
default=Path("~") / ".prefect",
|
1158
|
+
description="The path to the Prefect home directory. Defaults to ~/.prefect",
|
1159
|
+
)
|
1160
|
+
debug_mode: bool = Field(
|
1161
|
+
default=False,
|
1162
|
+
description="If True, enables debug mode which may provide additional logging and debugging features.",
|
1163
|
+
)
|
1540
1164
|
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1165
|
+
silence_api_url_misconfiguration: bool = Field(
|
1166
|
+
default=False,
|
1167
|
+
description="""
|
1168
|
+
If `True`, disable the warning when a user accidentally misconfigure its `PREFECT_API_URL`
|
1169
|
+
Sometimes when a user manually set `PREFECT_API_URL` to a custom url,reverse-proxy for example,
|
1170
|
+
we would like to silence this warning so we will set it to `FALSE`.
|
1171
|
+
""",
|
1172
|
+
)
|
1545
1173
|
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1174
|
+
client_max_retries: int = Field(
|
1175
|
+
default=5,
|
1176
|
+
ge=0,
|
1177
|
+
description="""
|
1178
|
+
The maximum number of retries to perform on failed HTTP requests.
|
1179
|
+
Defaults to 5. Set to 0 to disable retries.
|
1180
|
+
See `PREFECT_CLIENT_RETRY_EXTRA_CODES` for details on which HTTP status codes are
|
1181
|
+
retried.
|
1182
|
+
""",
|
1183
|
+
)
|
1552
1184
|
|
1553
|
-
|
1554
|
-
|
1185
|
+
client_retry_jitter_factor: float = Field(
|
1186
|
+
default=0.2,
|
1187
|
+
ge=0.0,
|
1188
|
+
description="""
|
1189
|
+
A value greater than or equal to zero to control the amount of jitter added to retried
|
1190
|
+
client requests. Higher values introduce larger amounts of jitter.
|
1191
|
+
Set to 0 to disable jitter. See `clamped_poisson_interval` for details on the how jitter
|
1192
|
+
can affect retry lengths.
|
1193
|
+
""",
|
1194
|
+
)
|
1555
1195
|
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1196
|
+
client_retry_extra_codes: ClientRetryExtraCodes = Field(
|
1197
|
+
default_factory=set,
|
1198
|
+
description="""
|
1199
|
+
A list of extra HTTP status codes to retry on. Defaults to an empty list.
|
1200
|
+
429, 502 and 503 are always retried. Please note that not all routes are idempotent and retrying
|
1201
|
+
may result in unexpected behavior.
|
1202
|
+
""",
|
1203
|
+
examples=["404,429,503", "429", {404, 429, 503}],
|
1204
|
+
)
|
1562
1205
|
|
1563
|
-
|
1564
|
-
|
1206
|
+
client_csrf_support_enabled: bool = Field(
|
1207
|
+
default=True,
|
1208
|
+
description="""
|
1209
|
+
Determines if CSRF token handling is active in the Prefect client for API
|
1210
|
+
requests.
|
1565
1211
|
|
1566
|
-
|
1567
|
-
|
1568
|
-
|
1569
|
-
)
|
1570
|
-
"""
|
1571
|
-
The directory to serve static files from. This should be used when running into permissions issues
|
1572
|
-
when attempting to serve the UI from the default directory (for example when running in a Docker container)
|
1573
|
-
"""
|
1212
|
+
When enabled (`True`), the client automatically manages CSRF tokens by
|
1213
|
+
retrieving, storing, and including them in applicable state-changing requests
|
1214
|
+
""",
|
1215
|
+
)
|
1574
1216
|
|
1575
|
-
|
1217
|
+
experimental_warn: bool = Field(
|
1218
|
+
default=True,
|
1219
|
+
description="If `True`, warn on usage of experimental features.",
|
1220
|
+
)
|
1576
1221
|
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
Which message broker implementation to use for the messaging system, should point to a
|
1582
|
-
module that exports a Publisher and Consumer class.
|
1583
|
-
"""
|
1222
|
+
profiles_path: Optional[Path] = Field(
|
1223
|
+
default=None,
|
1224
|
+
description="The path to a profiles configuration file.",
|
1225
|
+
)
|
1584
1226
|
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
Which cache implementation to use for the events system. Should point to a module that
|
1590
|
-
exports a Cache class.
|
1591
|
-
"""
|
1227
|
+
tasks_refresh_cache: bool = Field(
|
1228
|
+
default=False,
|
1229
|
+
description="If `True`, enables a refresh of cached results: re-executing the task will refresh the cached results.",
|
1230
|
+
)
|
1592
1231
|
|
1232
|
+
task_default_retries: int = Field(
|
1233
|
+
default=0,
|
1234
|
+
ge=0,
|
1235
|
+
description="This value sets the default number of retries for all tasks.",
|
1236
|
+
)
|
1593
1237
|
|
1594
|
-
|
1238
|
+
task_default_retry_delay_seconds: Union[int, float, list[float]] = Field(
|
1239
|
+
default=0,
|
1240
|
+
description="This value sets the default retry delay seconds for all tasks.",
|
1241
|
+
)
|
1595
1242
|
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
""
|
1243
|
+
task_run_tag_concurrency_slot_wait_seconds: int = Field(
|
1244
|
+
default=30,
|
1245
|
+
ge=0,
|
1246
|
+
description="The number of seconds to wait before retrying when a task run cannot secure a concurrency slot from the server.",
|
1247
|
+
)
|
1600
1248
|
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
""
|
1249
|
+
flow_default_retries: int = Field(
|
1250
|
+
default=0,
|
1251
|
+
ge=0,
|
1252
|
+
description="This value sets the default number of retries for all flows.",
|
1253
|
+
)
|
1605
1254
|
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
|
1255
|
+
flow_default_retry_delay_seconds: Union[int, float] = Field(
|
1256
|
+
default=0,
|
1257
|
+
description="This value sets the retry delay seconds for all flows.",
|
1258
|
+
)
|
1610
1259
|
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1260
|
+
local_storage_path: Optional[Path] = Field(
|
1261
|
+
default=None,
|
1262
|
+
description="The path to a block storage directory to store things in.",
|
1263
|
+
)
|
1615
1264
|
|
1616
|
-
|
1617
|
-
|
1618
|
-
The
|
1619
|
-
|
1265
|
+
memo_store_path: Optional[Path] = Field(
|
1266
|
+
default=None,
|
1267
|
+
description="The path to the memo store file.",
|
1268
|
+
)
|
1620
1269
|
|
1621
|
-
|
1622
|
-
|
1623
|
-
|
1624
|
-
|
1270
|
+
memoize_block_auto_registration: bool = Field(
|
1271
|
+
default=True,
|
1272
|
+
description="Controls whether or not block auto-registration on start",
|
1273
|
+
)
|
1625
1274
|
|
1626
|
-
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
1275
|
+
sqlalchemy_pool_size: Optional[int] = Field(
|
1276
|
+
default=None,
|
1277
|
+
description="Controls connection pool size when using a PostgreSQL database with the Prefect API. If not set, the default SQLAlchemy pool size will be used.",
|
1278
|
+
)
|
1630
1279
|
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1280
|
+
sqlalchemy_max_overflow: Optional[int] = Field(
|
1281
|
+
default=None,
|
1282
|
+
description="Controls maximum overflow of the connection pool when using a PostgreSQL database with the Prefect API. If not set, the default SQLAlchemy maximum overflow value will be used.",
|
1283
|
+
)
|
1635
1284
|
|
1636
|
-
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1285
|
+
async_fetch_state_result: bool = Field(
|
1286
|
+
default=False,
|
1287
|
+
description="""
|
1288
|
+
Determines whether `State.result()` fetches results automatically or not.
|
1289
|
+
In Prefect 2.6.0, the `State.result()` method was updated to be async
|
1290
|
+
to facilitate automatic retrieval of results from storage which means when
|
1291
|
+
writing async code you must `await` the call. For backwards compatibility,
|
1292
|
+
the result is not retrieved by default for async users. You may opt into this
|
1293
|
+
per call by passing `fetch=True` or toggle this setting to change the behavior
|
1294
|
+
globally.
|
1295
|
+
""",
|
1296
|
+
)
|
1640
1297
|
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1298
|
+
runner_process_limit: int = Field(
|
1299
|
+
default=5,
|
1300
|
+
description="Maximum number of processes a runner will execute in parallel.",
|
1301
|
+
)
|
1645
1302
|
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1303
|
+
runner_poll_frequency: int = Field(
|
1304
|
+
default=10,
|
1305
|
+
description="Number of seconds a runner should wait between queries for scheduled work.",
|
1306
|
+
)
|
1650
1307
|
|
1651
|
-
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
How long to cache related resource data for emitting server-side vents
|
1656
|
-
"""
|
1308
|
+
runner_server_missed_polls_tolerance: int = Field(
|
1309
|
+
default=2,
|
1310
|
+
description="Number of missed polls before a runner is considered unhealthy by its webserver.",
|
1311
|
+
)
|
1657
1312
|
|
1658
|
-
|
1659
|
-
|
1660
|
-
|
1661
|
-
|
1662
|
-
The maximum range to look back for backfilling events for a websocket subscriber
|
1663
|
-
"""
|
1313
|
+
runner_server_host: str = Field(
|
1314
|
+
default="localhost",
|
1315
|
+
description="The host address the runner's webserver should bind to.",
|
1316
|
+
)
|
1664
1317
|
|
1665
|
-
|
1666
|
-
|
1667
|
-
The
|
1668
|
-
|
1318
|
+
runner_server_port: int = Field(
|
1319
|
+
default=8080,
|
1320
|
+
description="The port the runner's webserver should bind to.",
|
1321
|
+
)
|
1669
1322
|
|
1323
|
+
runner_server_log_level: LogLevel = Field(
|
1324
|
+
default="error",
|
1325
|
+
description="The log level of the runner's webserver.",
|
1326
|
+
)
|
1670
1327
|
|
1671
|
-
|
1328
|
+
runner_server_enable: bool = Field(
|
1329
|
+
default=False,
|
1330
|
+
description="Whether or not to enable the runner's webserver.",
|
1331
|
+
)
|
1672
1332
|
|
1673
|
-
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
""
|
1333
|
+
deployment_concurrency_slot_wait_seconds: float = Field(
|
1334
|
+
default=30.0,
|
1335
|
+
ge=0.0,
|
1336
|
+
description=(
|
1337
|
+
"The number of seconds to wait before retrying when a deployment flow run"
|
1338
|
+
" cannot secure a concurrency slot from the server."
|
1339
|
+
),
|
1340
|
+
)
|
1678
1341
|
|
1679
|
-
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
1683
|
-
"""
|
1342
|
+
deployment_schedule_max_scheduled_runs: int = Field(
|
1343
|
+
default=50,
|
1344
|
+
description="The maximum number of scheduled runs to create for a deployment.",
|
1345
|
+
)
|
1684
1346
|
|
1685
|
-
|
1686
|
-
|
1687
|
-
|
1688
|
-
|
1347
|
+
worker_heartbeat_seconds: float = Field(
|
1348
|
+
default=30,
|
1349
|
+
description="Number of seconds a worker should wait between sending a heartbeat.",
|
1350
|
+
)
|
1689
1351
|
|
1352
|
+
worker_query_seconds: float = Field(
|
1353
|
+
default=10,
|
1354
|
+
description="Number of seconds a worker should wait between queries for scheduled work.",
|
1355
|
+
)
|
1690
1356
|
|
1691
|
-
|
1357
|
+
worker_prefetch_seconds: float = Field(
|
1358
|
+
default=10,
|
1359
|
+
description="The number of seconds into the future a worker should query for scheduled work.",
|
1360
|
+
)
|
1692
1361
|
|
1362
|
+
worker_webserver_host: str = Field(
|
1363
|
+
default="0.0.0.0",
|
1364
|
+
description="The host address the worker's webserver should bind to.",
|
1365
|
+
)
|
1693
1366
|
|
1694
|
-
|
1367
|
+
worker_webserver_port: int = Field(
|
1368
|
+
default=8080,
|
1369
|
+
description="The port the worker's webserver should bind to.",
|
1370
|
+
)
|
1695
1371
|
|
1696
|
-
|
1697
|
-
|
1698
|
-
|
1372
|
+
task_scheduling_default_storage_block: Optional[str] = Field(
|
1373
|
+
default=None,
|
1374
|
+
description="The `block-type/block-document` slug of a block to use as the default storage for autonomous tasks.",
|
1375
|
+
)
|
1699
1376
|
|
1700
|
-
|
1701
|
-
|
1377
|
+
task_scheduling_delete_failed_submissions: bool = Field(
|
1378
|
+
default=True,
|
1379
|
+
description="Whether or not to delete failed task submissions from the database.",
|
1380
|
+
)
|
1702
1381
|
|
1703
|
-
|
1704
|
-
|
1382
|
+
task_scheduling_max_scheduled_queue_size: int = Field(
|
1383
|
+
default=1000,
|
1384
|
+
description="The maximum number of scheduled tasks to queue for submission.",
|
1385
|
+
)
|
1705
1386
|
|
1706
|
-
|
1387
|
+
task_scheduling_max_retry_queue_size: int = Field(
|
1388
|
+
default=100,
|
1389
|
+
description="The maximum number of retries to queue for submission.",
|
1390
|
+
)
|
1707
1391
|
|
1392
|
+
task_scheduling_pending_task_timeout: timedelta = Field(
|
1393
|
+
default=timedelta(0),
|
1394
|
+
description="How long before a PENDING task are made available to another task worker.",
|
1395
|
+
)
|
1708
1396
|
|
1709
|
-
|
1710
|
-
|
1397
|
+
experimental_enable_schedule_concurrency: bool = Field(
|
1398
|
+
default=False,
|
1399
|
+
description="Whether or not to enable concurrency for scheduled tasks.",
|
1400
|
+
)
|
1711
1401
|
|
1402
|
+
default_result_storage_block: Optional[str] = Field(
|
1403
|
+
default=None,
|
1404
|
+
description="The `block-type/block-document` slug of a block to use as the default result storage.",
|
1405
|
+
)
|
1712
1406
|
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
1716
|
-
|
1717
|
-
setting.name: (setting.type, setting.field)
|
1718
|
-
for setting in SETTING_VARIABLES.values()
|
1719
|
-
},
|
1720
|
-
)
|
1407
|
+
default_work_pool_name: Optional[str] = Field(
|
1408
|
+
default=None,
|
1409
|
+
description="The default work pool to deploy to.",
|
1410
|
+
)
|
1721
1411
|
|
1412
|
+
default_docker_build_namespace: Optional[str] = Field(
|
1413
|
+
default=None,
|
1414
|
+
description="The default Docker namespace to use when building images.",
|
1415
|
+
)
|
1722
1416
|
|
1723
|
-
|
1724
|
-
|
1725
|
-
|
1726
|
-
|
1417
|
+
messaging_broker: str = Field(
|
1418
|
+
default="prefect.server.utilities.messaging.memory",
|
1419
|
+
description="Which message broker implementation to use for the messaging system, should point to a module that exports a Publisher and Consumer class.",
|
1420
|
+
)
|
1727
1421
|
|
1422
|
+
messaging_cache: str = Field(
|
1423
|
+
default="prefect.server.utilities.messaging.memory",
|
1424
|
+
description="Which cache implementation to use for the events system. Should point to a module that exports a Cache class.",
|
1425
|
+
)
|
1728
1426
|
|
1729
|
-
|
1730
|
-
|
1731
|
-
"""
|
1732
|
-
Contains validated Prefect settings.
|
1427
|
+
###########################################################################
|
1428
|
+
# allow deprecated access to PREFECT_SOME_SETTING_NAME
|
1733
1429
|
|
1734
|
-
|
1735
|
-
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
1430
|
+
def __getattribute__(self, name: str) -> Any:
|
1431
|
+
if name.startswith("PREFECT_"):
|
1432
|
+
field_name = env_var_to_attr_name(name)
|
1433
|
+
warnings.warn(
|
1434
|
+
f"Accessing `Settings().{name}` is deprecated. Use `Settings().{field_name}` instead.",
|
1435
|
+
DeprecationWarning,
|
1436
|
+
stacklevel=2,
|
1437
|
+
)
|
1438
|
+
return super().__getattribute__(field_name)
|
1439
|
+
return super().__getattribute__(name)
|
1739
1440
|
|
1740
|
-
|
1741
|
-
This is not recommended:
|
1742
|
-
```python
|
1743
|
-
from prefect.settings import Settings
|
1744
|
-
Settings().PREFECT_PROFILES_PATH # PosixPath('${PREFECT_HOME}/profiles.toml')
|
1745
|
-
```
|
1746
|
-
"""
|
1441
|
+
###########################################################################
|
1747
1442
|
|
1748
|
-
|
1749
|
-
|
1750
|
-
|
1751
|
-
"""
|
1752
|
-
value = getattr(self, setting.name)
|
1753
|
-
if setting.value_callback and not bypass_callback:
|
1754
|
-
value = setting.value_callback(self, value)
|
1755
|
-
return value
|
1443
|
+
@model_validator(mode="after")
|
1444
|
+
def post_hoc_settings(self) -> Self:
|
1445
|
+
"""refactor on resolution of https://github.com/pydantic/pydantic/issues/9789
|
1756
1446
|
|
1757
|
-
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1447
|
+
we should not be modifying __pydantic_fields_set__ directly, but until we can
|
1448
|
+
define dependencies between defaults in a first-class way, we need clean up
|
1449
|
+
post-hoc default assignments to keep set/unset fields correct after instantiation.
|
1450
|
+
"""
|
1451
|
+
if self.cloud_ui_url is None:
|
1452
|
+
self.cloud_ui_url = default_cloud_ui_url(self)
|
1453
|
+
self.__pydantic_fields_set__.remove("cloud_ui_url")
|
1454
|
+
|
1455
|
+
if self.ui_url is None:
|
1456
|
+
self.ui_url = default_ui_url(self)
|
1457
|
+
self.__pydantic_fields_set__.remove("ui_url")
|
1458
|
+
if self.ui_api_url is None:
|
1459
|
+
if self.api_url:
|
1460
|
+
self.ui_api_url = self.api_url
|
1461
|
+
self.__pydantic_fields_set__.remove("ui_api_url")
|
1462
|
+
else:
|
1463
|
+
self.ui_api_url = (
|
1464
|
+
f"http://{self.server_api_host}:{self.server_api_port}"
|
1465
|
+
)
|
1466
|
+
self.__pydantic_fields_set__.remove("ui_api_url")
|
1467
|
+
if self.profiles_path is None or "PREFECT_HOME" in str(self.profiles_path):
|
1468
|
+
self.profiles_path = Path(f"{self.home}/profiles.toml")
|
1469
|
+
self.__pydantic_fields_set__.remove("profiles_path")
|
1470
|
+
if self.local_storage_path is None:
|
1471
|
+
self.local_storage_path = Path(f"{self.home}/storage")
|
1472
|
+
self.__pydantic_fields_set__.remove("local_storage_path")
|
1473
|
+
if self.memo_store_path is None:
|
1474
|
+
self.memo_store_path = Path(f"{self.home}/memo_store.toml")
|
1475
|
+
self.__pydantic_fields_set__.remove("memo_store_path")
|
1476
|
+
if self.debug_mode or self.test_mode:
|
1477
|
+
self.logging_level = "DEBUG"
|
1478
|
+
self.logging_internal_level = "DEBUG"
|
1479
|
+
self.__pydantic_fields_set__.remove("logging_level")
|
1480
|
+
self.__pydantic_fields_set__.remove("logging_internal_level")
|
1481
|
+
|
1482
|
+
if self.logging_settings_path is None:
|
1483
|
+
self.logging_settings_path = Path(f"{self.home}/logging.yml")
|
1484
|
+
self.__pydantic_fields_set__.remove("logging_settings_path")
|
1485
|
+
# Set default database connection URL if not provided
|
1486
|
+
if self.api_database_connection_url is None:
|
1487
|
+
self.api_database_connection_url = default_database_connection_url(self)
|
1488
|
+
self.__pydantic_fields_set__.remove("api_database_connection_url")
|
1489
|
+
if "PREFECT_API_DATABASE_PASSWORD" in (
|
1490
|
+
db_url := (
|
1491
|
+
self.api_database_connection_url.get_secret_value()
|
1492
|
+
if isinstance(self.api_database_connection_url, SecretStr)
|
1493
|
+
else self.api_database_connection_url
|
1494
|
+
)
|
1495
|
+
):
|
1496
|
+
if self.api_database_password is None:
|
1497
|
+
raise ValueError(
|
1498
|
+
"database password is None - please set PREFECT_API_DATABASE_PASSWORD"
|
1499
|
+
)
|
1500
|
+
self.api_database_connection_url = SecretStr(
|
1501
|
+
db_url.replace(
|
1502
|
+
"${PREFECT_API_DATABASE_PASSWORD}",
|
1503
|
+
self.api_database_password.get_secret_value(),
|
1504
|
+
)
|
1505
|
+
if self.api_database_password
|
1506
|
+
else ""
|
1507
|
+
)
|
1508
|
+
self.__pydantic_fields_set__.remove("api_database_connection_url")
|
1509
|
+
return self
|
1763
1510
|
|
1764
1511
|
@model_validator(mode="after")
|
1765
1512
|
def emit_warnings(self):
|
1766
|
-
"""
|
1767
|
-
Add root validation functions for settings here.
|
1768
|
-
"""
|
1769
|
-
# TODO: We could probably register these dynamically but this is the simpler
|
1770
|
-
# approach for now. We can explore more interesting validation features
|
1771
|
-
# in the future.
|
1513
|
+
"""More post-hoc validation of settings, including warnings for misconfigurations."""
|
1772
1514
|
values = self.model_dump()
|
1773
1515
|
values = max_log_size_smaller_than_batch_size(values)
|
1774
1516
|
values = warn_on_database_password_value_without_usage(values)
|
1775
|
-
if not
|
1517
|
+
if not self.silence_api_url_misconfiguration:
|
1776
1518
|
values = warn_on_misconfigured_api_url(values)
|
1777
1519
|
return self
|
1778
1520
|
|
1521
|
+
##########################################################################
|
1522
|
+
# Settings methods
|
1523
|
+
|
1524
|
+
@classmethod
|
1525
|
+
def valid_setting_names(cls) -> Set[str]:
|
1526
|
+
"""
|
1527
|
+
A set of valid setting names, e.g. "PREFECT_API_URL" or "PREFECT_API_KEY".
|
1528
|
+
"""
|
1529
|
+
return set(
|
1530
|
+
f"{cls.model_config.get('env_prefix')}{key.upper()}"
|
1531
|
+
for key in cls.model_fields.keys()
|
1532
|
+
)
|
1533
|
+
|
1779
1534
|
def copy_with_update(
|
1780
|
-
self,
|
1535
|
+
self: Self,
|
1781
1536
|
updates: Optional[Mapping[Setting, Any]] = None,
|
1782
1537
|
set_defaults: Optional[Mapping[Setting, Any]] = None,
|
1783
1538
|
restore_defaults: Optional[Iterable[Setting]] = None,
|
1784
|
-
) ->
|
1539
|
+
) -> Self:
|
1785
1540
|
"""
|
1786
|
-
Create a new
|
1541
|
+
Create a new Settings object with validation.
|
1787
1542
|
|
1788
1543
|
Arguments:
|
1789
1544
|
updates: A mapping of settings to new values. Existing values for the
|
@@ -1793,101 +1548,110 @@ class Settings(SettingsFieldsMixin):
|
|
1793
1548
|
restore_defaults: An iterable of settings to restore to their default values.
|
1794
1549
|
|
1795
1550
|
Returns:
|
1796
|
-
A new
|
1551
|
+
A new Settings object.
|
1797
1552
|
"""
|
1553
|
+
restore_defaults_names = set(r.field_name for r in restore_defaults or [])
|
1798
1554
|
updates = updates or {}
|
1799
1555
|
set_defaults = set_defaults or {}
|
1800
|
-
restore_defaults = restore_defaults or set()
|
1801
|
-
restore_defaults_names = {setting.name for setting in restore_defaults}
|
1802
|
-
|
1803
|
-
return self.__class__(
|
1804
|
-
**{
|
1805
|
-
**{setting.name: value for setting, value in set_defaults.items()},
|
1806
|
-
**self.model_dump(exclude_unset=True, exclude=restore_defaults_names),
|
1807
|
-
**{setting.name: value for setting, value in updates.items()},
|
1808
|
-
}
|
1809
|
-
)
|
1810
1556
|
|
1811
|
-
|
1812
|
-
|
1813
|
-
|
1814
|
-
|
1815
|
-
settings = self.model_copy(
|
1816
|
-
update={
|
1817
|
-
setting.name: obfuscate(self.value_of(setting))
|
1818
|
-
for setting in SETTING_VARIABLES.values()
|
1819
|
-
if setting.is_secret
|
1820
|
-
# Exclude deprecated settings with null values to avoid warnings
|
1821
|
-
and not (setting.deprecated and self.value_of(setting) is None)
|
1822
|
-
}
|
1557
|
+
new_settings = self.__class__(
|
1558
|
+
**{setting.field_name: value for setting, value in set_defaults.items()}
|
1559
|
+
| self.model_dump(exclude_unset=True, exclude=restore_defaults_names)
|
1560
|
+
| {setting.field_name: value for setting, value in updates.items()}
|
1823
1561
|
)
|
1824
|
-
|
1825
|
-
# after we have updated their value above
|
1826
|
-
with warnings.catch_warnings():
|
1827
|
-
warnings.simplefilter(
|
1828
|
-
"ignore", category=pydantic.warnings.PydanticDeprecatedSince20
|
1829
|
-
)
|
1830
|
-
settings.__fields_set__.intersection_update(self.__fields_set__)
|
1831
|
-
return settings
|
1562
|
+
return new_settings
|
1832
1563
|
|
1833
1564
|
def hash_key(self) -> str:
|
1834
1565
|
"""
|
1835
1566
|
Return a hash key for the settings object. This is needed since some
|
1836
|
-
settings may be unhashable
|
1567
|
+
settings may be unhashable, like lists.
|
1837
1568
|
"""
|
1838
1569
|
env_variables = self.to_environment_variables()
|
1839
1570
|
return str(hash(tuple((key, value) for key, value in env_variables.items())))
|
1840
1571
|
|
1841
1572
|
def to_environment_variables(
|
1842
|
-
self,
|
1573
|
+
self,
|
1574
|
+
include: Optional[Iterable[Setting]] = None,
|
1575
|
+
exclude: Optional[Iterable[Setting]] = None,
|
1576
|
+
exclude_unset: bool = False,
|
1577
|
+
include_secrets: bool = True,
|
1843
1578
|
) -> Dict[str, str]:
|
1844
|
-
"""
|
1845
|
-
|
1846
|
-
|
1847
|
-
Note that setting values will not be run through their `value_callback` allowing
|
1848
|
-
dynamic resolution to occur when loaded from the returned environment.
|
1849
|
-
|
1850
|
-
Args:
|
1851
|
-
include_keys: An iterable of settings to include in the return value.
|
1852
|
-
If not set, all settings are used.
|
1853
|
-
exclude_unset: Only include settings that have been set (i.e. the value is
|
1854
|
-
not from the default). If set, unset keys will be dropped even if they
|
1855
|
-
are set in `include_keys`.
|
1856
|
-
|
1857
|
-
Returns:
|
1858
|
-
A dictionary of settings with values cast to strings
|
1859
|
-
"""
|
1860
|
-
include = set(include or SETTING_VARIABLES.values())
|
1579
|
+
"""Convert the settings object to a dictionary of environment variables."""
|
1580
|
+
included_names = {s.field_name for s in include} if include else None
|
1581
|
+
excluded_names = {s.field_name for s in exclude} if exclude else None
|
1861
1582
|
|
1862
1583
|
if exclude_unset:
|
1863
|
-
|
1864
|
-
|
1865
|
-
|
1866
|
-
|
1867
|
-
|
1868
|
-
include.intersection_update(set_keys)
|
1869
|
-
|
1870
|
-
# Validate the types of items in `include` to prevent exclusion bugs
|
1871
|
-
for key in include:
|
1872
|
-
if not isinstance(key, Setting):
|
1873
|
-
raise TypeError(
|
1874
|
-
"Invalid type {type(key).__name__!r} for key in `include`."
|
1584
|
+
if included_names is None:
|
1585
|
+
included_names = set(self.model_dump(exclude_unset=True).keys())
|
1586
|
+
else:
|
1587
|
+
included_names.intersection_update(
|
1588
|
+
{key for key in self.model_dump(exclude_unset=True)}
|
1875
1589
|
)
|
1876
1590
|
|
1877
|
-
env:
|
1878
|
-
|
1591
|
+
env: Dict[str, Any] = self.model_dump(
|
1592
|
+
include=included_names,
|
1593
|
+
exclude=excluded_names,
|
1594
|
+
mode="json",
|
1595
|
+
context={"include_secrets": include_secrets},
|
1879
1596
|
)
|
1597
|
+
return {
|
1598
|
+
f"{self.model_config.get('env_prefix')}{key.upper()}": str(value)
|
1599
|
+
for key, value in env.items()
|
1600
|
+
if value is not None
|
1601
|
+
}
|
1880
1602
|
|
1881
|
-
|
1882
|
-
|
1603
|
+
@model_serializer(
|
1604
|
+
mode="wrap", when_used="always"
|
1605
|
+
) # TODO: reconsider `when_used` default for more control
|
1606
|
+
def ser_model(
|
1607
|
+
self, handler: SerializerFunctionWrapHandler, info: SerializationInfo
|
1608
|
+
) -> Any:
|
1609
|
+
ctx = info.context
|
1610
|
+
jsonable_self = handler(self)
|
1611
|
+
if ctx and ctx.get("include_secrets") is True:
|
1612
|
+
dump_kwargs = dict(include=info.include, exclude=info.exclude)
|
1613
|
+
jsonable_self.update(
|
1614
|
+
{
|
1615
|
+
field_name: visit_collection(
|
1616
|
+
expr=getattr(self, field_name),
|
1617
|
+
visit_fn=partial(handle_secret_render, context=ctx),
|
1618
|
+
return_data=True,
|
1619
|
+
)
|
1620
|
+
for field_name in set(self.model_dump(**dump_kwargs).keys()) # type: ignore
|
1621
|
+
}
|
1622
|
+
)
|
1623
|
+
return jsonable_self
|
1883
1624
|
|
1884
|
-
model_config = ConfigDict(frozen=True)
|
1885
1625
|
|
1626
|
+
############################################################################
|
1627
|
+
# Settings utils
|
1886
1628
|
|
1887
1629
|
# Functions to instantiate `Settings` instances
|
1888
1630
|
|
1889
|
-
|
1890
|
-
|
1631
|
+
|
1632
|
+
def _cast_settings(
|
1633
|
+
settings: Union[Dict[Union[str, Setting], Any], Any],
|
1634
|
+
) -> Dict[Setting, Any]:
|
1635
|
+
"""For backwards compatibility, allow either Settings objects as keys or string references to settings."""
|
1636
|
+
if not isinstance(settings, dict):
|
1637
|
+
raise ValueError("Settings must be a dictionary.")
|
1638
|
+
casted_settings = {}
|
1639
|
+
for k, value in settings.items():
|
1640
|
+
try:
|
1641
|
+
if isinstance(k, str):
|
1642
|
+
field = Settings.model_fields[env_var_to_attr_name(k)]
|
1643
|
+
setting = Setting(
|
1644
|
+
name=k,
|
1645
|
+
default=field.default,
|
1646
|
+
type_=field.annotation,
|
1647
|
+
)
|
1648
|
+
else:
|
1649
|
+
setting = k
|
1650
|
+
casted_settings[setting] = value
|
1651
|
+
except KeyError as e:
|
1652
|
+
warnings.warn(f"Setting {e} is not recognized")
|
1653
|
+
continue
|
1654
|
+
return casted_settings
|
1891
1655
|
|
1892
1656
|
|
1893
1657
|
def get_current_settings() -> Settings:
|
@@ -1901,55 +1665,14 @@ def get_current_settings() -> Settings:
|
|
1901
1665
|
if settings_context is not None:
|
1902
1666
|
return settings_context.settings
|
1903
1667
|
|
1904
|
-
return
|
1905
|
-
|
1906
|
-
|
1907
|
-
def get_settings_from_env() -> Settings:
|
1908
|
-
"""
|
1909
|
-
Returns a settings object populated with default values and overrides from
|
1910
|
-
environment variables, ignoring any values in profiles.
|
1911
|
-
|
1912
|
-
Calls with the same environment return a cached object instead of reconstructing
|
1913
|
-
to avoid validation overhead.
|
1914
|
-
"""
|
1915
|
-
# Since os.environ is a Dict[str, str] we can safely hash it by contents, but we
|
1916
|
-
# must be careful to avoid hashing a generator instead of a tuple
|
1917
|
-
cache_key = hash(tuple((key, value) for key, value in os.environ.items()))
|
1918
|
-
|
1919
|
-
if cache_key not in _FROM_ENV_CACHE:
|
1920
|
-
_FROM_ENV_CACHE[cache_key] = Settings()
|
1921
|
-
|
1922
|
-
return _FROM_ENV_CACHE[cache_key]
|
1923
|
-
|
1924
|
-
|
1925
|
-
def get_default_settings() -> Settings:
|
1926
|
-
"""
|
1927
|
-
Returns a settings object populated with default values, ignoring any overrides
|
1928
|
-
from environment variables or profiles.
|
1929
|
-
|
1930
|
-
This is cached since the defaults should not change during the lifetime of the
|
1931
|
-
module.
|
1932
|
-
"""
|
1933
|
-
global _DEFAULTS_CACHE
|
1934
|
-
|
1935
|
-
if not _DEFAULTS_CACHE:
|
1936
|
-
old = os.environ
|
1937
|
-
try:
|
1938
|
-
os.environ = {}
|
1939
|
-
settings = get_settings_from_env()
|
1940
|
-
finally:
|
1941
|
-
os.environ = old
|
1942
|
-
|
1943
|
-
_DEFAULTS_CACHE = settings
|
1944
|
-
|
1945
|
-
return _DEFAULTS_CACHE
|
1668
|
+
return Settings()
|
1946
1669
|
|
1947
1670
|
|
1948
1671
|
@contextmanager
|
1949
1672
|
def temporary_settings(
|
1950
|
-
updates: Optional[Mapping[Setting
|
1951
|
-
set_defaults: Optional[Mapping[Setting
|
1952
|
-
restore_defaults: Optional[Iterable[Setting
|
1673
|
+
updates: Optional[Mapping[Setting, Any]] = None,
|
1674
|
+
set_defaults: Optional[Mapping[Setting, Any]] = None,
|
1675
|
+
restore_defaults: Optional[Iterable[Setting]] = None,
|
1953
1676
|
) -> Generator[Settings, None, None]:
|
1954
1677
|
"""
|
1955
1678
|
Temporarily override the current settings by entering a new profile.
|
@@ -1976,6 +1699,9 @@ def temporary_settings(
|
|
1976
1699
|
|
1977
1700
|
context = prefect.context.get_settings_context()
|
1978
1701
|
|
1702
|
+
if not restore_defaults:
|
1703
|
+
restore_defaults = []
|
1704
|
+
|
1979
1705
|
new_settings = context.settings.copy_with_update(
|
1980
1706
|
updates=updates, set_defaults=set_defaults, restore_defaults=restore_defaults
|
1981
1707
|
)
|
@@ -1986,52 +1712,40 @@ def temporary_settings(
|
|
1986
1712
|
yield new_settings
|
1987
1713
|
|
1988
1714
|
|
1989
|
-
|
1990
|
-
|
1991
|
-
A user profile containing settings.
|
1992
|
-
"""
|
1715
|
+
############################################################################
|
1716
|
+
# Profiles
|
1993
1717
|
|
1994
|
-
name: str
|
1995
|
-
settings: Dict[Setting, Any] = Field(default_factory=dict)
|
1996
|
-
source: Optional[Path] = None
|
1997
|
-
model_config = ConfigDict(extra="ignore", arbitrary_types_allowed=True)
|
1998
1718
|
|
1999
|
-
|
2000
|
-
|
2001
|
-
return validate_settings(value)
|
1719
|
+
class Profile(BaseModel):
|
1720
|
+
"""A user profile containing settings."""
|
2002
1721
|
|
2003
|
-
|
2004
|
-
"""
|
2005
|
-
Validate the settings contained in this profile.
|
1722
|
+
model_config = ConfigDict(extra="ignore", arbitrary_types_allowed=True)
|
2006
1723
|
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2010
|
-
|
2011
|
-
|
2012
|
-
# We do not return the `Settings` object because this is not the recommended
|
2013
|
-
# path for constructing settings with a profile. See `use_profile` instead.
|
2014
|
-
Settings(**{setting.name: value for setting, value in self.settings.items()})
|
1724
|
+
name: str
|
1725
|
+
settings: Annotated[Dict[Setting, Any], BeforeValidator(_cast_settings)] = Field(
|
1726
|
+
default_factory=dict
|
1727
|
+
)
|
1728
|
+
source: Optional[Path] = None
|
2015
1729
|
|
2016
|
-
def
|
2017
|
-
"""
|
2018
|
-
|
2019
|
-
|
1730
|
+
def to_environment_variables(self) -> Dict[str, str]:
|
1731
|
+
"""Convert the profile settings to a dictionary of environment variables."""
|
1732
|
+
return {
|
1733
|
+
setting.name: str(value)
|
1734
|
+
for setting, value in self.settings.items()
|
1735
|
+
if value is not None
|
1736
|
+
}
|
2020
1737
|
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
2024
|
-
|
2025
|
-
|
2026
|
-
|
2027
|
-
|
2028
|
-
|
2029
|
-
|
2030
|
-
|
2031
|
-
|
2032
|
-
)
|
2033
|
-
changed.append((setting, setting.deprecated_renamed_to))
|
2034
|
-
return changed
|
1738
|
+
def validate_settings(self):
|
1739
|
+
errors: List[Tuple[Setting, ValidationError]] = []
|
1740
|
+
for setting, value in self.settings.items():
|
1741
|
+
try:
|
1742
|
+
TypeAdapter(
|
1743
|
+
Settings.model_fields[setting.field_name].annotation
|
1744
|
+
).validate_python(value)
|
1745
|
+
except ValidationError as e:
|
1746
|
+
errors.append((setting, e))
|
1747
|
+
if errors:
|
1748
|
+
raise ProfileSettingsValidationError(errors)
|
2035
1749
|
|
2036
1750
|
|
2037
1751
|
class ProfilesCollection:
|
@@ -2077,7 +1791,10 @@ class ProfilesCollection:
|
|
2077
1791
|
self.active_name = name
|
2078
1792
|
|
2079
1793
|
def update_profile(
|
2080
|
-
self,
|
1794
|
+
self,
|
1795
|
+
name: str,
|
1796
|
+
settings: Dict[Setting, Any],
|
1797
|
+
source: Optional[Path] = None,
|
2081
1798
|
) -> Profile:
|
2082
1799
|
"""
|
2083
1800
|
Add a profile to the collection or update the existing on if the name is already
|
@@ -2155,9 +1872,7 @@ class ProfilesCollection:
|
|
2155
1872
|
return {
|
2156
1873
|
"active": self.active_name,
|
2157
1874
|
"profiles": {
|
2158
|
-
profile.name:
|
2159
|
-
setting.name: value for setting, value in profile.settings.items()
|
2160
|
-
}
|
1875
|
+
profile.name: profile.to_environment_variables()
|
2161
1876
|
for profile in self.profiles_by_name.values()
|
2162
1877
|
},
|
2163
1878
|
}
|
@@ -2217,8 +1932,9 @@ def _write_profiles_to(path: Path, profiles: ProfilesCollection) -> None:
|
|
2217
1932
|
Any existing data not present in the given `profiles` will be deleted.
|
2218
1933
|
"""
|
2219
1934
|
if not path.exists():
|
1935
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
2220
1936
|
path.touch(mode=0o600)
|
2221
|
-
|
1937
|
+
path.write_text(toml.dumps(profiles.to_dict()))
|
2222
1938
|
|
2223
1939
|
|
2224
1940
|
def load_profiles(include_defaults: bool = True) -> ProfilesCollection:
|
@@ -2226,24 +1942,32 @@ def load_profiles(include_defaults: bool = True) -> ProfilesCollection:
|
|
2226
1942
|
Load profiles from the current profile path. Optionally include profiles from the
|
2227
1943
|
default profile path.
|
2228
1944
|
"""
|
1945
|
+
current_settings = get_current_settings()
|
2229
1946
|
default_profiles = _read_profiles_from(DEFAULT_PROFILES_PATH)
|
2230
1947
|
|
1948
|
+
if current_settings.profiles_path is None:
|
1949
|
+
raise RuntimeError(
|
1950
|
+
"No profiles path set; please ensure `PREFECT_PROFILES_PATH` is set."
|
1951
|
+
)
|
1952
|
+
|
2231
1953
|
if not include_defaults:
|
2232
|
-
if not
|
1954
|
+
if not current_settings.profiles_path.exists():
|
2233
1955
|
return ProfilesCollection([])
|
2234
|
-
return _read_profiles_from(
|
1956
|
+
return _read_profiles_from(current_settings.profiles_path)
|
2235
1957
|
|
2236
|
-
user_profiles_path =
|
1958
|
+
user_profiles_path = current_settings.profiles_path
|
2237
1959
|
profiles = default_profiles
|
2238
1960
|
if user_profiles_path.exists():
|
2239
1961
|
user_profiles = _read_profiles_from(user_profiles_path)
|
2240
1962
|
|
2241
1963
|
# Merge all of the user profiles with the defaults
|
2242
1964
|
for name in user_profiles:
|
1965
|
+
if not (source := user_profiles[name].source):
|
1966
|
+
raise ValueError(f"Profile {name!r} has no source.")
|
2243
1967
|
profiles.update_profile(
|
2244
1968
|
name,
|
2245
1969
|
settings=user_profiles[name].settings,
|
2246
|
-
source=
|
1970
|
+
source=source,
|
2247
1971
|
)
|
2248
1972
|
|
2249
1973
|
if user_profiles.active_name:
|
@@ -2274,7 +1998,8 @@ def save_profiles(profiles: ProfilesCollection) -> None:
|
|
2274
1998
|
"""
|
2275
1999
|
Writes all non-default profiles to the current profiles path.
|
2276
2000
|
"""
|
2277
|
-
profiles_path =
|
2001
|
+
profiles_path = get_current_settings().profiles_path
|
2002
|
+
assert profiles_path is not None, "Profiles path is not set."
|
2278
2003
|
profiles = profiles.without_profile_source(DEFAULT_PROFILES_PATH)
|
2279
2004
|
return _write_profiles_to(profiles_path, profiles)
|
2280
2005
|
|
@@ -2290,7 +2015,9 @@ def load_profile(name: str) -> Profile:
|
|
2290
2015
|
raise ValueError(f"Profile {name!r} not found.")
|
2291
2016
|
|
2292
2017
|
|
2293
|
-
def update_current_profile(
|
2018
|
+
def update_current_profile(
|
2019
|
+
settings: Dict[Union[str, Setting], Any],
|
2020
|
+
) -> Profile:
|
2294
2021
|
"""
|
2295
2022
|
Update the persisted data for the profile currently in-use.
|
2296
2023
|
|
@@ -2307,6 +2034,8 @@ def update_current_profile(settings: Dict[Union[str, Setting], Any]) -> Profile:
|
|
2307
2034
|
current_profile = prefect.context.get_settings_context().profile
|
2308
2035
|
|
2309
2036
|
if not current_profile:
|
2037
|
+
from prefect.exceptions import MissingProfileError
|
2038
|
+
|
2310
2039
|
raise MissingProfileError("No profile is currently in use.")
|
2311
2040
|
|
2312
2041
|
profiles = load_profiles()
|
@@ -2314,11 +2043,64 @@ def update_current_profile(settings: Dict[Union[str, Setting], Any]) -> Profile:
|
|
2314
2043
|
# Ensure the current profile's settings are present
|
2315
2044
|
profiles.update_profile(current_profile.name, current_profile.settings)
|
2316
2045
|
# Then merge the new settings in
|
2317
|
-
new_profile = profiles.update_profile(
|
2046
|
+
new_profile = profiles.update_profile(
|
2047
|
+
current_profile.name, _cast_settings(settings)
|
2048
|
+
)
|
2318
2049
|
|
2319
|
-
# Validate before saving
|
2320
2050
|
new_profile.validate_settings()
|
2321
2051
|
|
2322
2052
|
save_profiles(profiles)
|
2323
2053
|
|
2324
2054
|
return profiles[current_profile.name]
|
2055
|
+
|
2056
|
+
|
2057
|
+
############################################################################
|
2058
|
+
# Allow traditional env var access
|
2059
|
+
|
2060
|
+
|
2061
|
+
class _SettingsDict(dict):
|
2062
|
+
"""allow either `field_name` or `ENV_VAR_NAME` access
|
2063
|
+
```
|
2064
|
+
d = _SettingsDict(Settings)
|
2065
|
+
d["api_url"] == d["PREFECT_API_URL"]
|
2066
|
+
```
|
2067
|
+
"""
|
2068
|
+
|
2069
|
+
def __init__(self: Self, settings_cls: Type[BaseSettings]):
|
2070
|
+
super().__init__()
|
2071
|
+
for field_name, field in settings_cls.model_fields.items():
|
2072
|
+
setting = Setting(
|
2073
|
+
name=f"{settings_cls.model_config.get('env_prefix')}{field_name.upper()}",
|
2074
|
+
default=field.default,
|
2075
|
+
type_=field.annotation,
|
2076
|
+
)
|
2077
|
+
self[field_name] = self[setting.name] = setting
|
2078
|
+
|
2079
|
+
|
2080
|
+
SETTING_VARIABLES: dict[str, Setting] = _SettingsDict(Settings)
|
2081
|
+
|
2082
|
+
|
2083
|
+
def __getattr__(name: str) -> Setting:
|
2084
|
+
if name in Settings.valid_setting_names():
|
2085
|
+
return SETTING_VARIABLES[name]
|
2086
|
+
raise AttributeError(f"{name} is not a Prefect setting.")
|
2087
|
+
|
2088
|
+
|
2089
|
+
__all__ = [ # noqa: F822
|
2090
|
+
"Profile",
|
2091
|
+
"ProfilesCollection",
|
2092
|
+
"Setting",
|
2093
|
+
"Settings",
|
2094
|
+
"load_current_profile",
|
2095
|
+
"update_current_profile",
|
2096
|
+
"load_profile",
|
2097
|
+
"save_profiles",
|
2098
|
+
"load_profiles",
|
2099
|
+
"get_current_settings",
|
2100
|
+
"temporary_settings",
|
2101
|
+
"DEFAULT_PROFILES_PATH",
|
2102
|
+
# add public settings here for auto-completion
|
2103
|
+
"PREFECT_API_KEY", # type: ignore
|
2104
|
+
"PREFECT_API_URL", # type: ignore
|
2105
|
+
"PREFECT_UI_URL", # type: ignore
|
2106
|
+
]
|