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/settings.py CHANGED
@@ -1,59 +1,28 @@
1
1
  """
2
- Prefect settings management.
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
- Each setting is defined as a `Setting` type. The name of each setting is stylized in all
5
- caps, matching the environment variable that can be used to change the setting.
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 string
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
- create_model,
76
- field_validator,
77
- fields,
46
+ Secret,
47
+ SecretStr,
48
+ SerializationInfo,
49
+ SerializerFunctionWrapHandler,
50
+ TypeAdapter,
51
+ ValidationError,
52
+ model_serializer,
78
53
  model_validator,
79
54
  )
80
- from pydantic_settings import BaseSettings, SettingsConfigDict
81
- from typing_extensions import Literal
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._internal.compatibility.deprecated import generate_deprecation_message
84
- from prefect._internal.schemas.validators import validate_settings
85
- from prefect.exceptions import MissingProfileError
86
- from prefect.utilities.names import OBFUSCATED_PREFIX, obfuscate
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
- class Setting(Generic[T]):
75
+ def env_var_to_attr_name(env_var: str) -> str:
96
76
  """
97
- Setting definition type.
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
- if deprecated_renamed_to is not None:
142
- # Track the deprecation both ways
143
- deprecated_renamed_to.deprecated_renamed_from = self
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
- Example:
150
- ```python
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
- return value
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
- if self._name:
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 deprecated_message(self):
217
- return generate_deprecation_message(
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
- def __repr__(self) -> str:
226
- return f"<{self.name}: {self.type!r}>"
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
- # Callbacks and validators
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
- def debug_mode_log_level(settings, value):
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
- def only_return_value_in_test_mode(settings, value):
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
- def default_ui_api_url(settings, value):
278
- """
279
- `value_callback` for `PREFECT_UI_API_URL` that sets the default value to
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
- def status_codes_as_integers_in_range(_, value):
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
- values = {v.strip() for v in value.split(",")}
177
+ # Update routing
178
+ ui_url = ui_url.replace("/accounts/", "/account/")
179
+ ui_url = ui_url.replace("/workspaces/", "/workspace/")
300
180
 
301
- if any(not v.isdigit() or int(v) < 100 or int(v) > 599 for v in values):
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
- def template_with_settings(*upstream_settings: Setting) -> Callable[["Settings", T], T]:
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
- def templater(settings, value):
318
- if value is None:
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
- original_type = type(value)
322
- template_values = {
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 templater
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
- value = values["PREFECT_API_DATABASE_PASSWORD"]
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
- value
357
- and not value.startswith(OBFUSCATED_PREFIX)
358
- and values["PREFECT_API_DATABASE_CONNECTION_URL"] is not None
359
- and (
360
- "PREFECT_API_DATABASE_PASSWORD"
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["PREFECT_API_URL"]
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", value: Optional[str]):
412
- driver = PREFECT_API_DATABASE_DRIVER.value_from(settings)
413
- if driver == "postgresql+asyncpg":
281
+ def default_database_connection_url(settings: "Settings") -> SecretStr:
282
+ value = None
283
+ if settings.api_database_driver == "postgresql+asyncpg":
414
284
  required = [
415
- PREFECT_API_DATABASE_HOST,
416
- PREFECT_API_DATABASE_USER,
417
- PREFECT_API_DATABASE_NAME,
418
- PREFECT_API_DATABASE_PASSWORD,
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=driver,
435
- host=PREFECT_API_DATABASE_HOST.value_from(settings),
436
- port=PREFECT_API_DATABASE_PORT.value_from(settings) or 5432,
437
- username=PREFECT_API_DATABASE_USER.value_from(settings),
438
- password=PREFECT_API_DATABASE_PASSWORD.value_from(settings),
439
- database=PREFECT_API_DATABASE_NAME.value_from(settings),
440
- query=[],
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 driver == "sqlite+aiosqlite":
444
- path = PREFECT_API_DATABASE_NAME.value_from(settings)
445
- if path:
446
- return f"{driver}:///{path}"
447
- elif driver:
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
- PREFECT_CLI_WRAP_LINES = Setting(
554
- bool,
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
- PREFECT_TEST_MODE = Setting(
562
- bool,
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
- PREFECT_TEST_SETTING = Setting(
587
- Any,
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
- PREFECT_API_SSL_CERT_FILE = Setting(
605
- Optional[str],
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
- PREFECT_API_URL = Setting(
615
- Optional[str],
616
- default=None,
617
- )
618
- """
619
- If provided, the URL of a hosted Prefect API. Defaults to `None`.
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
- PREFECT_SILENCE_API_URL_MISCONFIGURATION = Setting(
625
- bool,
626
- default=False,
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
- PREFECT_CLIENT_MAX_RETRIES = Setting(int, default=5)
650
- """
651
- The maximum number of retries to perform on failed HTTP requests.
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
- Defaults to 5.
654
- Set to 0 to disable retries.
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
- See `PREFECT_CLIENT_RETRY_EXTRA_CODES` for details on which HTTP status codes are
657
- retried.
658
- """
357
+ if not self.profiles_path.exists():
358
+ return {}
659
359
 
660
- PREFECT_CLIENT_RETRY_JITTER_FACTOR = Setting(float, default=0.2)
661
- """
662
- A value greater than or equal to zero to control the amount of jitter added to retried
663
- client requests. Higher values introduce larger amounts of jitter.
664
-
665
- Set to 0 to disable jitter. See `clamped_poisson_interval` for details on the how jitter
666
- can affect retry lengths.
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
- PREFECT_CLIENT_RETRY_EXTRA_CODES = Setting(
671
- str, default="", value_callback=status_codes_as_integers_in_range
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
- PREFECT_CLIENT_CSRF_SUPPORT_ENABLED = Setting(bool, default=True)
680
- """
681
- Determines if CSRF token handling is active in the Prefect client for API
682
- requests.
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
- Disabling this setting (`False`) means the client will not handle CSRF tokens,
689
- which might be suitable for environments where CSRF protection is disabled.
409
+ ###########################################################################
410
+ # Settings
690
411
 
691
- Defaults to `True`, ensuring CSRF protection is enabled by default.
692
- """
693
412
 
694
- PREFECT_CLOUD_API_URL = Setting(
695
- str,
696
- default="https://api.prefect.cloud/api",
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
- PREFECT_UI_URL = Setting(
702
- Optional[str],
703
- default=None,
704
- value_callback=default_ui_url,
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
- When using Prefect Cloud, this will include the account and workspace.
710
- When using an ephemeral server, this will be `None`.
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
- PREFECT_CLOUD_UI_URL = Setting(
715
- Optional[str],
716
- default=None,
717
- value_callback=default_cloud_ui_url,
718
- )
719
- """
720
- The URL for the Cloud UI. By default, this is inferred from the PREFECT_CLOUD_API_URL.
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
- Note: PREFECT_UI_URL will be workspace specific and will be usable in the open source too.
723
- In contrast, this value is only valid for Cloud and will not include the workspace.
724
- """
450
+ ###########################################################################
451
+ # CLI
725
452
 
726
- PREFECT_API_REQUEST_TIMEOUT = Setting(
727
- float,
728
- default=60.0,
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
- PREFECT_EXPERIMENTAL_WARN = Setting(bool, default=True)
733
- """
734
- If enabled, warn on usage of experimental features.
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
- PREFECT_PROFILES_PATH = Setting(
738
- Path,
739
- default=Path("${PREFECT_HOME}") / "profiles.toml",
740
- value_callback=template_with_settings(PREFECT_HOME),
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
- PREFECT_RESULTS_DEFAULT_SERIALIZER = Setting(
745
- str,
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
- PREFECT_RESULTS_PERSIST_BY_DEFAULT = Setting(
752
- bool,
753
- default=False,
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
- PREFECT_TASKS_REFRESH_CACHE = Setting(
761
- bool,
762
- default=False,
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
- PREFECT_TASK_DEFAULT_RETRIES = Setting(int, default=0)
770
- """
771
- This value sets the default number of retries for all tasks.
772
- This value does not overwrite individually set retries values on tasks
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
- PREFECT_FLOW_DEFAULT_RETRIES = Setting(int, default=0)
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
- PREFECT_FLOW_DEFAULT_RETRY_DELAY_SECONDS = Setting(Union[int, float], default=0)
782
- """
783
- This value sets the retry delay seconds for all flows.
784
- This value does not overwrite individually set retry delay seconds
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
- PREFECT_TASK_DEFAULT_RETRY_DELAY_SECONDS = Setting(
788
- Union[float, int, List[float]], default=0
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
- PREFECT_TASK_RUN_TAG_CONCURRENCY_SLOT_WAIT_SECONDS = Setting(int, default=30)
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
- PREFECT_LOCAL_STORAGE_PATH = Setting(
802
- Path,
803
- default=Path("${PREFECT_HOME}") / "storage",
804
- value_callback=template_with_settings(PREFECT_HOME),
805
- )
806
- """The path to a block storage directory to store things in."""
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
- PREFECT_MEMO_STORE_PATH = Setting(
809
- Path,
810
- default=Path("${PREFECT_HOME}") / "memo_store.toml",
811
- value_callback=template_with_settings(PREFECT_HOME),
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
- PREFECT_MEMOIZE_BLOCK_AUTO_REGISTRATION = Setting(
816
- bool,
817
- default=True,
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
- PREFECT_LOGGING_LEVEL = Setting(
826
- str,
827
- default="INFO",
828
- value_callback=debug_mode_log_level,
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
- PREFECT_LOGGING_INTERNAL_LEVEL = Setting(
837
- str,
838
- default="ERROR",
839
- value_callback=debug_mode_log_level,
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
- PREFECT_LOGGING_SERVER_LEVEL = Setting(
847
- str,
848
- default="WARNING",
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
- PREFECT_LOGGING_SETTINGS_PATH = Setting(
853
- Path,
854
- default=Path("${PREFECT_HOME}") / "logging.yml",
855
- value_callback=template_with_settings(PREFECT_HOME),
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
- PREFECT_LOGGING_EXTRA_LOGGERS = Setting(
864
- str,
865
- default="",
866
- value_callback=get_extra_loggers,
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
- PREFECT_LOGGING_LOG_PRINTS = Setting(
876
- bool,
877
- default=False,
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
- PREFECT_LOGGING_TO_API_ENABLED = Setting(
885
- bool,
886
- default=True,
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
- PREFECT_LOGGING_TO_API_BATCH_INTERVAL = Setting(float, default=2.0)
894
- """The number of seconds between batched writes of logs to the API."""
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
- PREFECT_LOGGING_TO_API_BATCH_SIZE = Setting(
897
- int,
898
- default=4_000_000,
899
- )
900
- """The maximum size in bytes for a batch of logs."""
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
- PREFECT_LOGGING_TO_API_MAX_LOG_SIZE = Setting(
903
- int,
904
- default=1_000_000,
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
- PREFECT_LOGGING_TO_API_WHEN_MISSING_FLOW = Setting(
909
- Literal["warn", "error", "ignore"],
910
- default="warn",
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
- All logs sent to the API must be associated with a flow run. The API log handler can
917
- only be used outside of a flow by manually providing a flow run identifier. Logs
918
- that are not associated with a flow run will not be sent to the API. This setting can
919
- be used to determine if a warning or error is displayed when the identifier is missing.
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
- The following options are available:
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
- - "warn": Log a warning message.
924
- - "error": Raise an error.
925
- - "ignore": Do not log a warning message or raise an error.
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
- PREFECT_SQLALCHEMY_POOL_SIZE = Setting(
929
- Optional[int],
930
- default=None,
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
- PREFECT_SQLALCHEMY_MAX_OVERFLOW = Setting(
937
- Optional[int],
938
- default=None,
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
- PREFECT_LOGGING_COLORS = Setting(
945
- bool,
946
- default=True,
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
- PREFECT_LOGGING_MARKUP = Setting(
951
- bool,
952
- default=False,
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
- PREFECT_ASYNC_FETCH_STATE_RESULT = Setting(bool, default=False)
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
- PREFECT_API_BLOCKS_REGISTER_ON_START = Setting(
978
- bool,
979
- default=True,
980
- )
981
- """
982
- If set, any block types that have been imported will be registered with the
983
- backend on application startup. If not set, block types must be manually
984
- registered.
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
- PREFECT_API_DATABASE_CONNECTION_URL = Setting(
988
- Optional[str],
989
- default=None,
990
- value_callback=default_database_connection_url,
991
- is_secret=True,
992
- )
993
- """
994
- A database connection URL in a SQLAlchemy-compatible
995
- format. Prefect currently supports SQLite and Postgres. Note that all
996
- Prefect database engines must use an async driver - for SQLite, use
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
- PREFECT_API_DATABASE_DRIVER = Setting(
1017
- Optional[Literal["postgresql+asyncpg", "sqlite+aiosqlite"]],
1018
- default=None,
1019
- )
1020
- """
1021
- The database driver to use when connecting to the database.
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
- PREFECT_API_DATABASE_HOST = Setting(Optional[str], default=None)
1025
- """
1026
- The database server host.
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
- PREFECT_API_DATABASE_PORT = Setting(Optional[int], default=None)
1030
- """
1031
- The database server port.
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
- PREFECT_API_DATABASE_USER = Setting(Optional[str], default=None)
1035
- """
1036
- The user to use when connecting to the database.
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
- PREFECT_API_DATABASE_NAME = Setting(Optional[str], default=None)
1040
- """
1041
- The name of the Prefect database on the remote server, or the path to the database file
1042
- for SQLite.
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
- PREFECT_API_DATABASE_PASSWORD = Setting(
1046
- Optional[str],
1047
- default=None,
1048
- is_secret=True,
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
- PREFECT_API_DATABASE_ECHO = Setting(
1057
- bool,
1058
- default=False,
1059
- )
1060
- """If `True`, SQLAlchemy will log all SQL issued to the database. Defaults to `False`."""
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
- PREFECT_API_DATABASE_MIGRATE_ON_START = Setting(
1063
- bool,
1064
- default=True,
1065
- )
1066
- """If `True`, the database will be upgraded on application creation. If `False`, the database will need to be upgraded manually."""
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
- PREFECT_API_DATABASE_TIMEOUT = Setting(
1069
- Optional[float],
1070
- default=10.0,
1071
- )
1072
- """
1073
- A statement timeout, in seconds, applied to all database interactions made by the API.
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
- PREFECT_API_DATABASE_CONNECTION_TIMEOUT = Setting(
1078
- Optional[float],
1079
- default=5,
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
- PREFECT_API_SERVICES_SCHEDULER_LOOP_SECONDS = Setting(
1086
- float,
1087
- default=60,
1088
- )
1089
- """The scheduler loop interval, in seconds. This determines
1090
- how often the scheduler will attempt to schedule new flow runs, but has no
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
- PREFECT_API_SERVICES_SCHEDULER_DEPLOYMENT_BATCH_SIZE = Setting(
1096
- int,
1097
- default=100,
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
- PREFECT_API_SERVICES_SCHEDULER_MAX_RUNS = Setting(
1107
- int,
1108
- default=100,
1109
- )
1110
- """The scheduler will attempt to schedule up to this many
1111
- auto-scheduled runs in the future. Note that runs may have fewer than
1112
- this many scheduled runs, depending on the value of
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
- PREFECT_API_SERVICES_SCHEDULER_MIN_RUNS = Setting(
1117
- int,
1118
- default=3,
1119
- )
1120
- """The scheduler will attempt to schedule at least this many
1121
- auto-scheduled runs in the future. Note that runs may have more than
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
- PREFECT_API_SERVICES_SCHEDULER_MAX_SCHEDULED_TIME = Setting(
1127
- timedelta,
1128
- default=timedelta(days=100),
1129
- )
1130
- """The scheduler will create new runs up to this far in the
1131
- future. Note that this setting will take precedence over
1132
- `scheduler_max_runs`: if a flow runs once a month and
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
- PREFECT_API_SERVICES_SCHEDULER_MIN_SCHEDULED_TIME = Setting(
1138
- timedelta,
1139
- default=timedelta(hours=1),
1140
- )
1141
- """The scheduler will create new runs at least this far in the
1142
- future. Note that this setting will take precedence over `scheduler_min_runs`:
1143
- if a flow runs every hour and `scheduler_min_scheduled_time` is three hours,
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
- PREFECT_API_SERVICES_SCHEDULER_INSERT_BATCH_SIZE = Setting(
1149
- int,
1150
- default=500,
1151
- )
1152
- """The number of flow runs the scheduler will attempt to insert
1153
- in one batch across all deployments. If the number of flow runs to
1154
- schedule exceeds this amount, the runs will be inserted in batches of this size.
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
- PREFECT_API_SERVICES_LATE_RUNS_LOOP_SECONDS = Setting(
1159
- float,
1160
- default=5,
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
- PREFECT_API_SERVICES_LATE_RUNS_AFTER_SECONDS = Setting(
1167
- timedelta,
1168
- default=timedelta(seconds=15),
1169
- )
1170
- """The late runs service will mark runs as late after they
1171
- have exceeded their scheduled start time by this many seconds. Defaults
1172
- to `5` seconds.
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
- PREFECT_API_SERVICES_PAUSE_EXPIRATIONS_LOOP_SECONDS = Setting(
1176
- float,
1177
- default=5,
1178
- )
1179
- """The pause expiration service will look for runs to mark as failed
1180
- this often. Defaults to `5`.
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
- PREFECT_API_SERVICES_CANCELLATION_CLEANUP_LOOP_SECONDS = Setting(
1184
- float,
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
- PREFECT_API_SERVICES_FOREMAN_ENABLED = Setting(bool, default=True)
1192
- """Whether or not to start the Foreman service in the server application."""
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
- PREFECT_API_SERVICES_FOREMAN_LOOP_SECONDS = Setting(float, default=15)
1195
- """The number of seconds to wait between each iteration of the Foreman loop which checks
1196
- for offline workers and updates work pool status."""
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
- PREFECT_API_SERVICES_FOREMAN_INACTIVITY_HEARTBEAT_MULTIPLE = Setting(int, default=3)
1200
- "The number of heartbeats that must be missed before a worker is marked as offline."
838
+ logging_level: LogLevel = Field(
839
+ default="INFO",
840
+ description="The default logging level for Prefect loggers.",
841
+ )
1201
842
 
1202
- PREFECT_API_SERVICES_FOREMAN_FALLBACK_HEARTBEAT_INTERVAL_SECONDS = Setting(
1203
- int, default=30
1204
- )
1205
- """The number of seconds to use for online/offline evaluation if a worker's heartbeat
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
- PREFECT_API_SERVICES_FOREMAN_DEPLOYMENT_LAST_POLLED_TIMEOUT_SECONDS = Setting(
1209
- int, default=60
1210
- )
1211
- """The number of seconds before a deployment is marked as not ready if it has not been
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
- PREFECT_API_SERVICES_FOREMAN_WORK_QUEUE_LAST_POLLED_TIMEOUT_SECONDS = Setting(
1215
- int, default=60
1216
- )
1217
- """The number of seconds before a work queue is marked as not ready if it has not been
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
- PREFECT_API_LOG_RETRYABLE_ERRORS = Setting(bool, default=False)
1221
- """If `True`, log retryable errors in the API and it's services."""
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
- PREFECT_API_SERVICES_TASK_RUN_RECORDER_ENABLED = Setting(bool, default=True)
1224
- """
1225
- Whether or not to start the task run recorder service in the server application.
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
- PREFECT_API_DEFAULT_LIMIT = Setting(
1230
- int,
1231
- default=200,
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
- PREFECT_SERVER_API_HOST = Setting(
1238
- str,
1239
- default="127.0.0.1",
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
- PREFECT_SERVER_API_PORT = Setting(
1244
- int,
1245
- default=4200,
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
- PREFECT_SERVER_API_KEEPALIVE_TIMEOUT = Setting(
1250
- int,
1251
- default=5,
1252
- )
1253
- """
1254
- The API's keep alive timeout (defaults to `5`).
1255
- Refer to https://www.uvicorn.org/settings/#timeouts for details.
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
- When the API is hosted behind a load balancer, you may want to set this to a value
1258
- greater than the load balancer's idle timeout.
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
- Note this setting only applies when calling `prefect server start`; if hosting the
1261
- API with another tool you will need to configure this there instead.
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
- PREFECT_SERVER_CSRF_PROTECTION_ENABLED = Setting(bool, default=False)
1265
- """
1266
- Controls the activation of CSRF protection for the Prefect server API.
927
+ ###########################################################################
928
+ # Server settings
1267
929
 
1268
- When enabled (`True`), the server enforces CSRF validation checks on incoming
1269
- state-changing requests (POST, PUT, PATCH, DELETE), requiring a valid CSRF
1270
- token to be included in the request headers or body. This adds a layer of
1271
- security by preventing unauthorized or malicious sites from making requests on
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
- It is recommended to enable this setting in production environments where the
1275
- API is exposed to web clients to safeguard against CSRF attacks.
935
+ server_api_port: int = Field(
936
+ default=4200,
937
+ description="The API's port address (defaults to `4200`).",
938
+ )
1276
939
 
1277
- Note: Enabling this setting requires corresponding support in the client for
1278
- CSRF token management. See PREFECT_CLIENT_CSRF_SUPPORT_ENABLED for more.
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
- PREFECT_SERVER_CSRF_TOKEN_EXPIRATION = Setting(timedelta, default=timedelta(hours=1))
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
- The default expiration time is set to 1 hour, which offers a reasonable
1287
- compromise. Adjust this setting based on your specific security requirements
1288
- and usage patterns.
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
- PREFECT_SERVER_CORS_ALLOWED_ORIGINS = Setting(
1292
- str,
1293
- default="*",
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
- By default, this is set to `*`, which allows requests from all origins.
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
- PREFECT_SERVER_CORS_ALLOWED_METHODS = Setting(
1302
- str,
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
- By default, this is set to `*`, which allows requests with all methods.
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
- PREFECT_SERVER_CORS_ALLOWED_HEADERS = Setting(
1312
- str,
1313
- default="*",
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
- By default, this is set to `*`, which allows requests with all headers.
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
- PREFECT_SERVER_ALLOW_EPHEMERAL_MODE = Setting(bool, default=False)
1322
- """
1323
- Controls whether or not a subprocess server can be started when no API URL is provided.
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
- PREFECT_SERVER_EPHEMERAL_STARTUP_TIMEOUT_SECONDS = Setting(
1327
- int,
1328
- default=10,
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
- PREFECT_UI_ENABLED = Setting(
1335
- bool,
1336
- default=True,
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
- PREFECT_UI_API_URL = Setting(
1341
- Optional[str],
1342
- default=None,
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
- PREFECT_SERVER_ANALYTICS_ENABLED = Setting(
1352
- bool,
1353
- default=True,
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
- PREFECT_API_SERVICES_SCHEDULER_ENABLED = Setting(
1361
- bool,
1362
- default=True,
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
- PREFECT_API_SERVICES_LATE_RUNS_ENABLED = Setting(
1369
- bool,
1370
- default=True,
1371
- )
1372
- """Whether or not to start the late runs service in the server application.
1373
- If disabled, you will need to run this service separately to have runs past their
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
- PREFECT_API_SERVICES_FLOW_RUN_NOTIFICATIONS_ENABLED = Setting(
1378
- bool,
1379
- default=True,
1380
- )
1381
- """Whether or not to start the flow run notifications service in the server application.
1382
- If disabled, you will need to run this service separately to send flow run notifications.
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
- PREFECT_API_SERVICES_PAUSE_EXPIRATIONS_ENABLED = Setting(
1386
- bool,
1387
- default=True,
1388
- )
1389
- """Whether or not to start the paused flow run expiration service in the server
1390
- application. If disabled, paused flows that have timed out will remain in a Paused state
1391
- until a resume attempt.
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
- PREFECT_API_TASK_CACHE_KEY_MAX_LENGTH = Setting(int, default=2000)
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
- PREFECT_API_SERVICES_CANCELLATION_CLEANUP_ENABLED = Setting(
1401
- bool,
1402
- default=True,
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
- PREFECT_API_MAX_FLOW_RUN_GRAPH_NODES = Setting(int, default=10000)
1410
- """
1411
- The maximum size of a flow run graph on the v2 API
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
- PREFECT_API_MAX_FLOW_RUN_GRAPH_ARTIFACTS = Setting(int, default=10000)
1415
- """
1416
- The maximum number of artifacts to show on a flow run graph on the v2 API
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
- # Prefect Events feature flags
1053
+ ui_serve_base: str = Field(
1054
+ default="/",
1055
+ description="The base URL path to serve the Prefect UI from.",
1056
+ )
1420
1057
 
1421
- PREFECT_RUNNER_PROCESS_LIMIT = Setting(int, default=5)
1422
- """
1423
- Maximum number of processes a runner will execute in parallel.
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
- PREFECT_RUNNER_POLL_FREQUENCY = Setting(int, default=10)
1427
- """
1428
- Number of seconds a runner should wait between queries for scheduled work.
1429
- """
1063
+ ###########################################################################
1064
+ # Events settings
1430
1065
 
1431
- PREFECT_RUNNER_SERVER_MISSED_POLLS_TOLERANCE = Setting(int, default=2)
1432
- """
1433
- Number of missed polls before a runner is considered unhealthy by its webserver.
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
- PREFECT_RUNNER_SERVER_HOST = Setting(str, default="localhost")
1437
- """
1438
- The host address the runner's webserver should bind to.
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
- PREFECT_RUNNER_SERVER_PORT = Setting(int, default=8080)
1442
- """
1443
- The port the runner's webserver should bind to.
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
- PREFECT_RUNNER_SERVER_LOG_LEVEL = Setting(str, default="error")
1447
- """
1448
- The log level of the runner's webserver.
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
- PREFECT_RUNNER_SERVER_ENABLE = Setting(bool, default=False)
1452
- """
1453
- Whether or not to enable the runner's webserver.
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
- PREFECT_DEPLOYMENT_SCHEDULE_MAX_SCHEDULED_RUNS = Setting(int, default=50)
1457
- """
1458
- The maximum number of scheduled runs to create for a deployment.
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
- PREFECT_WORKER_HEARTBEAT_SECONDS = Setting(float, default=30)
1462
- """
1463
- Number of seconds a worker should wait between sending a heartbeat.
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
- PREFECT_WORKER_QUERY_SECONDS = Setting(float, default=10)
1467
- """
1468
- Number of seconds a worker should wait between queries for scheduled flow runs.
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
- PREFECT_WORKER_PREFETCH_SECONDS = Setting(float, default=10)
1472
- """
1473
- The number of seconds into the future a worker should query for scheduled flow runs.
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
- PREFECT_WORKER_WEBSERVER_HOST = Setting(
1478
- str,
1479
- default="0.0.0.0",
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
- PREFECT_WORKER_WEBSERVER_PORT = Setting(
1486
- int,
1487
- default=8080,
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
- PREFECT_TASK_SCHEDULING_DEFAULT_STORAGE_BLOCK = Setting(Optional[str], default=None)
1494
- """The `block-type/block-document` slug of a block to use as the default storage
1495
- for autonomous tasks."""
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
- PREFECT_TASK_SCHEDULING_DELETE_FAILED_SUBMISSIONS = Setting(
1498
- bool,
1499
- default=True,
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
- PREFECT_TASK_SCHEDULING_MAX_SCHEDULED_QUEUE_SIZE = Setting(
1506
- int,
1507
- default=1000,
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
- PREFECT_TASK_SCHEDULING_MAX_RETRY_QUEUE_SIZE = Setting(
1514
- int,
1515
- default=100,
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
- PREFECT_TASK_SCHEDULING_PENDING_TASK_TIMEOUT = Setting(
1522
- timedelta,
1523
- default=timedelta(0),
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
- PREFECT_EXPERIMENTAL_ENABLE_SCHEDULE_CONCURRENCY = Setting(bool, default=False)
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
- # Defaults -----------------------------------------------------------------------------
1153
+ ###########################################################################
1154
+ # uncategorized
1534
1155
 
1535
- PREFECT_DEFAULT_RESULT_STORAGE_BLOCK = Setting(
1536
- Optional[str],
1537
- default=None,
1538
- )
1539
- """The `block-type/block-document` slug of a block to use as the default result storage."""
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
- PREFECT_DEFAULT_WORK_POOL_NAME = Setting(Optional[str], default=None)
1542
- """
1543
- The default work pool to deploy to.
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
- PREFECT_DEFAULT_DOCKER_BUILD_NAMESPACE = Setting(
1547
- Optional[str],
1548
- default=None,
1549
- )
1550
- """
1551
- The default Docker namespace to use when building images.
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
- Can be either an organization/username or a registry URL with an organization/username.
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
- PREFECT_UI_SERVE_BASE = Setting(
1557
- str,
1558
- default="/",
1559
- )
1560
- """
1561
- The base URL path to serve the Prefect UI from.
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
- Defaults to the root path.
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
- PREFECT_UI_STATIC_DIRECTORY = Setting(
1567
- Optional[str],
1568
- default=None,
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
- # Messaging system settings
1217
+ experimental_warn: bool = Field(
1218
+ default=True,
1219
+ description="If `True`, warn on usage of experimental features.",
1220
+ )
1576
1221
 
1577
- PREFECT_MESSAGING_BROKER = Setting(
1578
- str, default="prefect.server.utilities.messaging.memory"
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
- PREFECT_MESSAGING_CACHE = Setting(
1586
- str, default="prefect.server.utilities.messaging.memory"
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
- # Events settings
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
- PREFECT_EVENTS_MAXIMUM_LABELS_PER_RESOURCE = Setting(int, default=500)
1597
- """
1598
- The maximum number of labels a resource may have.
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
- PREFECT_EVENTS_MAXIMUM_RELATED_RESOURCES = Setting(int, default=500)
1602
- """
1603
- The maximum number of related resources an Event may have.
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
- PREFECT_EVENTS_MAXIMUM_SIZE_BYTES = Setting(int, default=1_500_000)
1607
- """
1608
- The maximum size of an Event when serialized to JSON
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
- PREFECT_API_SERVICES_TRIGGERS_ENABLED = Setting(bool, default=True)
1612
- """
1613
- Whether or not to start the triggers service in the server application.
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
- PREFECT_EVENTS_EXPIRED_BUCKET_BUFFER = Setting(timedelta, default=timedelta(seconds=60))
1617
- """
1618
- The amount of time to retain expired automation buckets
1619
- """
1265
+ memo_store_path: Optional[Path] = Field(
1266
+ default=None,
1267
+ description="The path to the memo store file.",
1268
+ )
1620
1269
 
1621
- PREFECT_EVENTS_PROACTIVE_GRANULARITY = Setting(timedelta, default=timedelta(seconds=5))
1622
- """
1623
- How frequently proactive automations are evaluated
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
- PREFECT_API_SERVICES_EVENT_PERSISTER_ENABLED = Setting(bool, default=True)
1627
- """
1628
- Whether or not to start the event persister service in the server application.
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
- PREFECT_API_SERVICES_EVENT_PERSISTER_BATCH_SIZE = Setting(int, default=20, gt=0)
1632
- """
1633
- The number of events the event persister will attempt to insert in one batch.
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
- PREFECT_API_SERVICES_EVENT_PERSISTER_FLUSH_INTERVAL = Setting(float, default=5, gt=0.0)
1637
- """
1638
- The maximum number of seconds between flushes of the event persister.
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
- PREFECT_EVENTS_RETENTION_PERIOD = Setting(timedelta, default=timedelta(days=7))
1642
- """
1643
- The amount of time to retain events in the database.
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
- PREFECT_API_EVENTS_STREAM_OUT_ENABLED = Setting(bool, default=True)
1647
- """
1648
- Whether or not to allow streaming events out of via websockets.
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
- PREFECT_API_EVENTS_RELATED_RESOURCE_CACHE_TTL = Setting(
1652
- timedelta, default=timedelta(minutes=5)
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
- PREFECT_EVENTS_MAXIMUM_WEBSOCKET_BACKFILL = Setting(
1659
- timedelta, default=timedelta(minutes=15)
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
- PREFECT_EVENTS_WEBSOCKET_BACKFILL_PAGE_SIZE = Setting(int, default=250, gt=0)
1666
- """
1667
- The page size for the queries to backfill events for websocket subscribers
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
- # Metrics settings
1328
+ runner_server_enable: bool = Field(
1329
+ default=False,
1330
+ description="Whether or not to enable the runner's webserver.",
1331
+ )
1672
1332
 
1673
- PREFECT_API_ENABLE_METRICS = Setting(bool, default=False)
1674
- """
1675
- Whether or not to enable Prometheus metrics in the server application. Metrics are
1676
- served at the path /api/metrics on the API server.
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
- PREFECT_CLIENT_ENABLE_METRICS = Setting(bool, default=False)
1680
- """
1681
- Whether or not to enable Prometheus metrics in the client SDK. Metrics are served
1682
- at the path /metrics.
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
- PREFECT_CLIENT_METRICS_PORT = Setting(int, default=4201)
1686
- """
1687
- The port to expose the client Prometheus metrics on.
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
- # Deprecated settings ------------------------------------------------------------------
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
- # Collect all defined settings ---------------------------------------------------------
1367
+ worker_webserver_port: int = Field(
1368
+ default=8080,
1369
+ description="The port the worker's webserver should bind to.",
1370
+ )
1695
1371
 
1696
- SETTING_VARIABLES: Dict[str, Any] = {
1697
- name: val for name, val in tuple(globals().items()) if isinstance(val, Setting)
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
- # Populate names in settings objects from assignments above
1701
- # Uses `__` to avoid setting these as global variables which can lead to sneaky bugs
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
- for __name, __setting in SETTING_VARIABLES.items():
1704
- __setting._name = __name
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
- # Dynamically create a pydantic model that includes all of our settings
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
- class PrefectBaseSettings(BaseSettings):
1710
- model_config = SettingsConfigDict(extra="ignore")
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
- SettingsFieldsMixin: Type[BaseSettings] = create_model(
1714
- "SettingsFieldsMixin",
1715
- __base__=PrefectBaseSettings, # Inheriting from `BaseSettings` provides environment variable loading
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
- # Defining a class after this that inherits the dynamic class rather than setting
1724
- # __base__ to the following class ensures that mkdocstrings properly generates
1725
- # reference documentation. It does not support module-level variables, even if they are
1726
- # an object which has __doc__ set.
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
- @add_cloudpickle_reduction
1730
- class Settings(SettingsFieldsMixin):
1731
- """
1732
- Contains validated Prefect settings.
1427
+ ###########################################################################
1428
+ # allow deprecated access to PREFECT_SOME_SETTING_NAME
1733
1429
 
1734
- Settings should be accessed using the relevant `Setting` object. For example:
1735
- ```python
1736
- from prefect.settings import PREFECT_HOME
1737
- PREFECT_HOME.value()
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
- Accessing a setting attribute directly will ignore any `value_callback` mutations.
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
- def value_of(self, setting: Setting[T], bypass_callback: bool = False) -> T:
1749
- """
1750
- Retrieve a setting's value.
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
- @field_validator(PREFECT_LOGGING_LEVEL.name, PREFECT_LOGGING_SERVER_LEVEL.name)
1758
- def check_valid_log_level(cls, value):
1759
- if isinstance(value, str):
1760
- value = value.upper()
1761
- logging._checkLevel(value)
1762
- return value
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 values["PREFECT_SILENCE_API_URL_MISCONFIGURATION"]:
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
- ) -> "Settings":
1539
+ ) -> Self:
1785
1540
  """
1786
- Create a new `Settings` object with validation.
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 `Settings` object.
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
- def with_obfuscated_secrets(self):
1812
- """
1813
- Returns a copy of this settings object with secret setting values obfuscated.
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
- # Ensure that settings that have not been marked as "set" before are still so
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. An example is lists.
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, include: Optional[Iterable[Setting]] = None, exclude_unset: bool = False
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
- Convert the settings object to environment variables.
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
- set_keys = {
1864
- # Collect all of the "set" keys and cast to `Setting` objects
1865
- SETTING_VARIABLES[key]
1866
- for key in self.model_dump(exclude_unset=True)
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: dict[str, Any] = self.model_dump(
1878
- mode="json", include={s.name for s in include}
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
- # Cast to strings and drop null values
1882
- return {key: str(value) for key, value in env.items() if value is not None}
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
- _DEFAULTS_CACHE: Optional[Settings] = None
1890
- _FROM_ENV_CACHE: Dict[int, Settings] = {}
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 get_settings_from_env()
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[T], Any]] = None,
1951
- set_defaults: Optional[Mapping[Setting[T], Any]] = None,
1952
- restore_defaults: Optional[Iterable[Setting[T]]] = None,
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
- class Profile(BaseModel):
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
- @field_validator("settings", mode="before")
2000
- def map_names_to_settings(cls, value):
2001
- return validate_settings(value)
1719
+ class Profile(BaseModel):
1720
+ """A user profile containing settings."""
2002
1721
 
2003
- def validate_settings(self) -> None:
2004
- """
2005
- Validate the settings contained in this profile.
1722
+ model_config = ConfigDict(extra="ignore", arbitrary_types_allowed=True)
2006
1723
 
2007
- Raises:
2008
- pydantic.ValidationError: When settings do not have valid values.
2009
- """
2010
- # Create a new `Settings` instance with the settings from this profile relying
2011
- # on Pydantic validation to raise an error.
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 convert_deprecated_renamed_settings(self) -> List[Tuple[Setting, Setting]]:
2017
- """
2018
- Update settings in place to replace deprecated settings with new settings when
2019
- renamed.
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
- Returns a list of tuples with the old and new setting.
2022
- """
2023
- changed = []
2024
- for setting in tuple(self.settings):
2025
- if (
2026
- setting.deprecated
2027
- and setting.deprecated_renamed_to
2028
- and setting.deprecated_renamed_to not in self.settings
2029
- ):
2030
- self.settings[setting.deprecated_renamed_to] = self.settings.pop(
2031
- setting
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, name: str, settings: Mapping[Union[Dict, str], Any], source: Path = None
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
- return path.write_text(toml.dumps(profiles.to_dict()))
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 PREFECT_PROFILES_PATH.value().exists():
1954
+ if not current_settings.profiles_path.exists():
2233
1955
  return ProfilesCollection([])
2234
- return _read_profiles_from(PREFECT_PROFILES_PATH.value())
1956
+ return _read_profiles_from(current_settings.profiles_path)
2235
1957
 
2236
- user_profiles_path = PREFECT_PROFILES_PATH.value()
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=user_profiles[name].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 = PREFECT_PROFILES_PATH.value()
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(settings: Dict[Union[str, Setting], Any]) -> 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(current_profile.name, settings)
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
+ ]