prefect-client 3.0.10__py3-none-any.whl → 3.0.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prefect/__init__.py +17 -14
- prefect/_internal/schemas/bases.py +1 -0
- prefect/_internal/schemas/validators.py +5 -3
- prefect/_version.py +3 -3
- prefect/client/cloud.py +2 -2
- prefect/client/orchestration.py +4 -4
- prefect/client/schemas/filters.py +14 -0
- prefect/context.py +3 -2
- prefect/deployments/runner.py +15 -6
- prefect/events/schemas/automations.py +3 -3
- prefect/events/schemas/deployment_triggers.py +10 -5
- prefect/flow_engine.py +4 -4
- prefect/flows.py +24 -9
- prefect/futures.py +4 -4
- prefect/logging/handlers.py +1 -1
- prefect/logging/highlighters.py +2 -0
- prefect/logging/logging.yml +82 -83
- prefect/runner/runner.py +1 -2
- prefect/runner/server.py +12 -1
- prefect/settings/__init__.py +59 -0
- prefect/settings/base.py +131 -0
- prefect/settings/constants.py +8 -0
- prefect/settings/context.py +65 -0
- prefect/settings/legacy.py +167 -0
- prefect/settings/models/__init__.py +0 -0
- prefect/settings/models/api.py +41 -0
- prefect/settings/models/cli.py +31 -0
- prefect/settings/models/client.py +90 -0
- prefect/settings/models/cloud.py +58 -0
- prefect/settings/models/deployments.py +40 -0
- prefect/settings/models/flows.py +37 -0
- prefect/settings/models/internal.py +21 -0
- prefect/settings/models/logging.py +137 -0
- prefect/settings/models/results.py +47 -0
- prefect/settings/models/root.py +447 -0
- prefect/settings/models/runner.py +65 -0
- prefect/settings/models/server/__init__.py +1 -0
- prefect/settings/models/server/api.py +133 -0
- prefect/settings/models/server/database.py +202 -0
- prefect/settings/models/server/deployments.py +24 -0
- prefect/settings/models/server/ephemeral.py +34 -0
- prefect/settings/models/server/events.py +140 -0
- prefect/settings/models/server/flow_run_graph.py +34 -0
- prefect/settings/models/server/root.py +143 -0
- prefect/settings/models/server/services.py +485 -0
- prefect/settings/models/server/tasks.py +86 -0
- prefect/settings/models/server/ui.py +52 -0
- prefect/settings/models/tasks.py +91 -0
- prefect/settings/models/testing.py +52 -0
- prefect/settings/models/ui.py +0 -0
- prefect/settings/models/worker.py +46 -0
- prefect/settings/profiles.py +390 -0
- prefect/settings/sources.py +162 -0
- prefect/task_engine.py +24 -29
- prefect/task_runners.py +6 -1
- prefect/tasks.py +63 -28
- prefect/utilities/asyncutils.py +1 -1
- prefect/utilities/engine.py +11 -3
- prefect/utilities/services.py +3 -3
- prefect/workers/base.py +8 -2
- {prefect_client-3.0.10.dist-info → prefect_client-3.0.11.dist-info}/METADATA +2 -2
- {prefect_client-3.0.10.dist-info → prefect_client-3.0.11.dist-info}/RECORD +66 -33
- prefect/settings.py +0 -2172
- /prefect/{profiles.toml → settings/profiles.toml} +0 -0
- {prefect_client-3.0.10.dist-info → prefect_client-3.0.11.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.10.dist-info → prefect_client-3.0.11.dist-info}/WHEEL +0 -0
- {prefect_client-3.0.10.dist-info → prefect_client-3.0.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,447 @@
|
|
1
|
+
import warnings
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import (
|
4
|
+
TYPE_CHECKING,
|
5
|
+
Annotated,
|
6
|
+
Any,
|
7
|
+
Iterable,
|
8
|
+
Mapping,
|
9
|
+
Optional,
|
10
|
+
)
|
11
|
+
from urllib.parse import urlparse
|
12
|
+
|
13
|
+
from pydantic import BeforeValidator, Field, SecretStr, model_validator
|
14
|
+
from pydantic_settings import SettingsConfigDict
|
15
|
+
from typing_extensions import Self
|
16
|
+
|
17
|
+
from prefect.settings.base import PrefectBaseSettings
|
18
|
+
from prefect.settings.models.tasks import TasksSettings
|
19
|
+
from prefect.settings.models.testing import TestingSettings
|
20
|
+
from prefect.settings.models.worker import WorkerSettings
|
21
|
+
from prefect.utilities.collections import deep_merge_dicts, set_in_dict
|
22
|
+
|
23
|
+
from .api import APISettings
|
24
|
+
from .cli import CLISettings
|
25
|
+
from .client import ClientSettings
|
26
|
+
from .cloud import CloudSettings
|
27
|
+
from .deployments import DeploymentsSettings
|
28
|
+
from .flows import FlowsSettings
|
29
|
+
from .internal import InternalSettings
|
30
|
+
from .logging import LoggingSettings
|
31
|
+
from .results import ResultsSettings
|
32
|
+
from .runner import RunnerSettings
|
33
|
+
from .server import ServerSettings
|
34
|
+
|
35
|
+
if TYPE_CHECKING:
|
36
|
+
from prefect.settings.legacy import Setting
|
37
|
+
|
38
|
+
|
39
|
+
class Settings(PrefectBaseSettings):
|
40
|
+
"""
|
41
|
+
Settings for Prefect using Pydantic settings.
|
42
|
+
|
43
|
+
See https://docs.pydantic.dev/latest/concepts/pydantic_settings
|
44
|
+
"""
|
45
|
+
|
46
|
+
model_config = SettingsConfigDict(
|
47
|
+
env_file=".env",
|
48
|
+
env_prefix="PREFECT_",
|
49
|
+
env_nested_delimiter=None,
|
50
|
+
extra="ignore",
|
51
|
+
)
|
52
|
+
|
53
|
+
home: Annotated[Path, BeforeValidator(lambda x: Path(x).expanduser())] = Field(
|
54
|
+
default=Path("~") / ".prefect",
|
55
|
+
description="The path to the Prefect home directory. Defaults to ~/.prefect",
|
56
|
+
)
|
57
|
+
|
58
|
+
profiles_path: Optional[Path] = Field(
|
59
|
+
default=None,
|
60
|
+
description="The path to a profiles configuration file.",
|
61
|
+
)
|
62
|
+
|
63
|
+
debug_mode: bool = Field(
|
64
|
+
default=False,
|
65
|
+
description="If True, enables debug mode which may provide additional logging and debugging features.",
|
66
|
+
)
|
67
|
+
|
68
|
+
api: APISettings = Field(
|
69
|
+
default_factory=APISettings,
|
70
|
+
description="Settings for interacting with the Prefect API",
|
71
|
+
)
|
72
|
+
|
73
|
+
cli: CLISettings = Field(
|
74
|
+
default_factory=CLISettings,
|
75
|
+
description="Settings for controlling CLI behavior",
|
76
|
+
)
|
77
|
+
|
78
|
+
client: ClientSettings = Field(
|
79
|
+
default_factory=ClientSettings,
|
80
|
+
description="Settings for for controlling API client behavior",
|
81
|
+
)
|
82
|
+
|
83
|
+
cloud: CloudSettings = Field(
|
84
|
+
default_factory=CloudSettings,
|
85
|
+
description="Settings for interacting with Prefect Cloud",
|
86
|
+
)
|
87
|
+
|
88
|
+
deployments: DeploymentsSettings = Field(
|
89
|
+
default_factory=DeploymentsSettings,
|
90
|
+
description="Settings for configuring deployments defaults",
|
91
|
+
)
|
92
|
+
|
93
|
+
flows: FlowsSettings = Field(
|
94
|
+
default_factory=FlowsSettings,
|
95
|
+
description="Settings for controlling flow behavior",
|
96
|
+
)
|
97
|
+
|
98
|
+
internal: InternalSettings = Field(
|
99
|
+
default_factory=InternalSettings,
|
100
|
+
description="Settings for internal Prefect machinery",
|
101
|
+
)
|
102
|
+
|
103
|
+
logging: LoggingSettings = Field(
|
104
|
+
default_factory=LoggingSettings,
|
105
|
+
description="Settings for controlling logging behavior",
|
106
|
+
)
|
107
|
+
|
108
|
+
results: ResultsSettings = Field(
|
109
|
+
default_factory=ResultsSettings,
|
110
|
+
description="Settings for controlling result storage behavior",
|
111
|
+
)
|
112
|
+
|
113
|
+
runner: RunnerSettings = Field(
|
114
|
+
default_factory=RunnerSettings,
|
115
|
+
description="Settings for controlling runner behavior",
|
116
|
+
)
|
117
|
+
|
118
|
+
server: ServerSettings = Field(
|
119
|
+
default_factory=ServerSettings,
|
120
|
+
description="Settings for controlling server behavior",
|
121
|
+
)
|
122
|
+
|
123
|
+
tasks: TasksSettings = Field(
|
124
|
+
default_factory=TasksSettings,
|
125
|
+
description="Settings for controlling task behavior",
|
126
|
+
)
|
127
|
+
|
128
|
+
testing: TestingSettings = Field(
|
129
|
+
default_factory=TestingSettings,
|
130
|
+
description="Settings used during testing",
|
131
|
+
)
|
132
|
+
|
133
|
+
worker: WorkerSettings = Field(
|
134
|
+
default_factory=WorkerSettings,
|
135
|
+
description="Settings for controlling worker behavior",
|
136
|
+
)
|
137
|
+
|
138
|
+
ui_url: Optional[str] = Field(
|
139
|
+
default=None,
|
140
|
+
description="The URL of the Prefect UI. If not set, the client will attempt to infer it.",
|
141
|
+
)
|
142
|
+
|
143
|
+
silence_api_url_misconfiguration: bool = Field(
|
144
|
+
default=False,
|
145
|
+
description="""
|
146
|
+
If `True`, disable the warning when a user accidentally misconfigure its `PREFECT_API_URL`
|
147
|
+
Sometimes when a user manually set `PREFECT_API_URL` to a custom url,reverse-proxy for example,
|
148
|
+
we would like to silence this warning so we will set it to `FALSE`.
|
149
|
+
""",
|
150
|
+
)
|
151
|
+
|
152
|
+
experimental_warn: bool = Field(
|
153
|
+
default=True,
|
154
|
+
description="If `True`, warn on usage of experimental features.",
|
155
|
+
)
|
156
|
+
|
157
|
+
# this setting needs to be removed
|
158
|
+
async_fetch_state_result: bool = Field(
|
159
|
+
default=False,
|
160
|
+
description="""
|
161
|
+
Determines whether `State.result()` fetches results automatically or not.
|
162
|
+
In Prefect 2.6.0, the `State.result()` method was updated to be async
|
163
|
+
to facilitate automatic retrieval of results from storage which means when
|
164
|
+
writing async code you must `await` the call. For backwards compatibility,
|
165
|
+
the result is not retrieved by default for async users. You may opt into this
|
166
|
+
per call by passing `fetch=True` or toggle this setting to change the behavior
|
167
|
+
globally.
|
168
|
+
""",
|
169
|
+
)
|
170
|
+
|
171
|
+
experimental_enable_schedule_concurrency: bool = Field(
|
172
|
+
default=False,
|
173
|
+
description="Whether or not to enable concurrency for scheduled tasks.",
|
174
|
+
)
|
175
|
+
|
176
|
+
###########################################################################
|
177
|
+
# allow deprecated access to PREFECT_SOME_SETTING_NAME
|
178
|
+
|
179
|
+
def __getattribute__(self, name: str) -> Any:
|
180
|
+
from prefect.settings.legacy import _env_var_to_accessor
|
181
|
+
|
182
|
+
if name.startswith("PREFECT_"):
|
183
|
+
accessor = _env_var_to_accessor(name)
|
184
|
+
warnings.warn(
|
185
|
+
f"Accessing `Settings().{name}` is deprecated. Use `Settings().{accessor}` instead.",
|
186
|
+
DeprecationWarning,
|
187
|
+
stacklevel=2,
|
188
|
+
)
|
189
|
+
path = accessor.split(".")
|
190
|
+
value = super().__getattribute__(path[0])
|
191
|
+
for key in path[1:]:
|
192
|
+
value = getattr(value, key)
|
193
|
+
return value
|
194
|
+
return super().__getattribute__(name)
|
195
|
+
|
196
|
+
###########################################################################
|
197
|
+
|
198
|
+
@model_validator(mode="after")
|
199
|
+
def post_hoc_settings(self) -> Self:
|
200
|
+
"""refactor on resolution of https://github.com/pydantic/pydantic/issues/9789
|
201
|
+
|
202
|
+
we should not be modifying __pydantic_fields_set__ directly, but until we can
|
203
|
+
define dependencies between defaults in a first-class way, we need clean up
|
204
|
+
post-hoc default assignments to keep set/unset fields correct after instantiation.
|
205
|
+
"""
|
206
|
+
if self.ui_url is None:
|
207
|
+
self.ui_url = _default_ui_url(self)
|
208
|
+
self.__pydantic_fields_set__.remove("ui_url")
|
209
|
+
if self.server.ui.api_url is None:
|
210
|
+
if self.api.url:
|
211
|
+
self.server.ui.api_url = self.api.url
|
212
|
+
self.server.ui.__pydantic_fields_set__.remove("api_url")
|
213
|
+
else:
|
214
|
+
self.server.ui.api_url = (
|
215
|
+
f"http://{self.server.api.host}:{self.server.api.port}/api"
|
216
|
+
)
|
217
|
+
self.server.ui.__pydantic_fields_set__.remove("api_url")
|
218
|
+
if self.profiles_path is None or "PREFECT_HOME" in str(self.profiles_path):
|
219
|
+
self.profiles_path = Path(f"{self.home}/profiles.toml")
|
220
|
+
self.__pydantic_fields_set__.remove("profiles_path")
|
221
|
+
if self.results.local_storage_path is None:
|
222
|
+
self.results.local_storage_path = Path(f"{self.home}/storage")
|
223
|
+
self.results.__pydantic_fields_set__.remove("local_storage_path")
|
224
|
+
if self.server.memo_store_path is None:
|
225
|
+
self.server.memo_store_path = Path(f"{self.home}/memo_store.toml")
|
226
|
+
self.server.__pydantic_fields_set__.remove("memo_store_path")
|
227
|
+
if self.debug_mode or self.testing.test_mode:
|
228
|
+
self.logging.level = "DEBUG"
|
229
|
+
self.internal.logging_level = "DEBUG"
|
230
|
+
self.logging.__pydantic_fields_set__.remove("level")
|
231
|
+
self.internal.__pydantic_fields_set__.remove("logging_level")
|
232
|
+
|
233
|
+
if self.logging.config_path is None:
|
234
|
+
self.logging.config_path = Path(f"{self.home}/logging.yml")
|
235
|
+
self.logging.__pydantic_fields_set__.remove("config_path")
|
236
|
+
# Set default database connection URL if not provided
|
237
|
+
if self.server.database.connection_url is None:
|
238
|
+
self.server.database.connection_url = _default_database_connection_url(self)
|
239
|
+
self.server.database.__pydantic_fields_set__.remove("connection_url")
|
240
|
+
db_url = (
|
241
|
+
self.server.database.connection_url.get_secret_value()
|
242
|
+
if isinstance(self.server.database.connection_url, SecretStr)
|
243
|
+
else self.server.database.connection_url
|
244
|
+
)
|
245
|
+
if (
|
246
|
+
"PREFECT_API_DATABASE_PASSWORD" in db_url
|
247
|
+
or "PREFECT_SERVER_DATABASE_PASSWORD" in db_url
|
248
|
+
):
|
249
|
+
if self.server.database.password is None:
|
250
|
+
raise ValueError(
|
251
|
+
"database password is None - please set PREFECT_SERVER_DATABASE_PASSWORD"
|
252
|
+
)
|
253
|
+
db_url = db_url.replace(
|
254
|
+
"${PREFECT_API_DATABASE_PASSWORD}",
|
255
|
+
self.server.database.password.get_secret_value()
|
256
|
+
if self.server.database.password
|
257
|
+
else "",
|
258
|
+
)
|
259
|
+
db_url = db_url.replace(
|
260
|
+
"${PREFECT_SERVER_DATABASE_PASSWORD}",
|
261
|
+
self.server.database.password.get_secret_value()
|
262
|
+
if self.server.database.password
|
263
|
+
else "",
|
264
|
+
)
|
265
|
+
self.server.database.connection_url = SecretStr(db_url)
|
266
|
+
self.server.database.__pydantic_fields_set__.remove("connection_url")
|
267
|
+
return self
|
268
|
+
|
269
|
+
@model_validator(mode="after")
|
270
|
+
def emit_warnings(self) -> Self:
|
271
|
+
"""More post-hoc validation of settings, including warnings for misconfigurations."""
|
272
|
+
if not self.silence_api_url_misconfiguration:
|
273
|
+
_warn_on_misconfigured_api_url(self)
|
274
|
+
return self
|
275
|
+
|
276
|
+
##########################################################################
|
277
|
+
# Settings methods
|
278
|
+
|
279
|
+
def copy_with_update(
|
280
|
+
self: Self,
|
281
|
+
updates: Optional[Mapping["Setting", Any]] = None,
|
282
|
+
set_defaults: Optional[Mapping["Setting", Any]] = None,
|
283
|
+
restore_defaults: Optional[Iterable["Setting"]] = None,
|
284
|
+
) -> Self:
|
285
|
+
"""
|
286
|
+
Create a new Settings object with validation.
|
287
|
+
|
288
|
+
Arguments:
|
289
|
+
updates: A mapping of settings to new values. Existing values for the
|
290
|
+
given settings will be overridden.
|
291
|
+
set_defaults: A mapping of settings to new default values. Existing values for
|
292
|
+
the given settings will only be overridden if they were not set.
|
293
|
+
restore_defaults: An iterable of settings to restore to their default values.
|
294
|
+
|
295
|
+
Returns:
|
296
|
+
A new Settings object.
|
297
|
+
"""
|
298
|
+
restore_defaults_obj = {}
|
299
|
+
for r in restore_defaults or []:
|
300
|
+
set_in_dict(restore_defaults_obj, r.accessor, True)
|
301
|
+
updates = updates or {}
|
302
|
+
set_defaults = set_defaults or {}
|
303
|
+
|
304
|
+
set_defaults_obj = {}
|
305
|
+
for setting, value in set_defaults.items():
|
306
|
+
set_in_dict(set_defaults_obj, setting.accessor, value)
|
307
|
+
|
308
|
+
updates_obj = {}
|
309
|
+
for setting, value in updates.items():
|
310
|
+
set_in_dict(updates_obj, setting.accessor, value)
|
311
|
+
|
312
|
+
new_settings = self.__class__.model_validate(
|
313
|
+
deep_merge_dicts(
|
314
|
+
set_defaults_obj,
|
315
|
+
self.model_dump(exclude_unset=True, exclude=restore_defaults_obj),
|
316
|
+
updates_obj,
|
317
|
+
)
|
318
|
+
)
|
319
|
+
return new_settings
|
320
|
+
|
321
|
+
def hash_key(self) -> str:
|
322
|
+
"""
|
323
|
+
Return a hash key for the settings object. This is needed since some
|
324
|
+
settings may be unhashable, like lists.
|
325
|
+
"""
|
326
|
+
env_variables = self.to_environment_variables()
|
327
|
+
return str(hash(tuple((key, value) for key, value in env_variables.items())))
|
328
|
+
|
329
|
+
|
330
|
+
def _default_ui_url(settings: "Settings") -> Optional[str]:
|
331
|
+
value = settings.ui_url
|
332
|
+
if value is not None:
|
333
|
+
return value
|
334
|
+
|
335
|
+
# Otherwise, infer a value from the API URL
|
336
|
+
ui_url = api_url = settings.api.url
|
337
|
+
|
338
|
+
if not api_url:
|
339
|
+
return None
|
340
|
+
assert ui_url is not None
|
341
|
+
|
342
|
+
cloud_url = settings.cloud.api_url
|
343
|
+
cloud_ui_url = settings.cloud.ui_url
|
344
|
+
if api_url.startswith(cloud_url) and cloud_ui_url:
|
345
|
+
ui_url = ui_url.replace(cloud_url, cloud_ui_url)
|
346
|
+
|
347
|
+
if ui_url.endswith("/api"):
|
348
|
+
# Handles open-source APIs
|
349
|
+
ui_url = ui_url[:-4]
|
350
|
+
|
351
|
+
# Handles Cloud APIs with content after `/api`
|
352
|
+
ui_url = ui_url.replace("/api/", "/")
|
353
|
+
|
354
|
+
# Update routing
|
355
|
+
ui_url = ui_url.replace("/accounts/", "/account/")
|
356
|
+
ui_url = ui_url.replace("/workspaces/", "/workspace/")
|
357
|
+
|
358
|
+
return ui_url
|
359
|
+
|
360
|
+
|
361
|
+
def _warn_on_misconfigured_api_url(settings: "Settings"):
|
362
|
+
"""
|
363
|
+
Validator for settings warning if the API URL is misconfigured.
|
364
|
+
"""
|
365
|
+
api_url = settings.api.url
|
366
|
+
if api_url is not None:
|
367
|
+
misconfigured_mappings = {
|
368
|
+
"app.prefect.cloud": (
|
369
|
+
"`PREFECT_API_URL` points to `app.prefect.cloud`. Did you"
|
370
|
+
" mean `api.prefect.cloud`?"
|
371
|
+
),
|
372
|
+
"account/": (
|
373
|
+
"`PREFECT_API_URL` uses `/account/` but should use `/accounts/`."
|
374
|
+
),
|
375
|
+
"workspace/": (
|
376
|
+
"`PREFECT_API_URL` uses `/workspace/` but should use `/workspaces/`."
|
377
|
+
),
|
378
|
+
}
|
379
|
+
warnings_list = []
|
380
|
+
|
381
|
+
for misconfig, warning in misconfigured_mappings.items():
|
382
|
+
if misconfig in api_url:
|
383
|
+
warnings_list.append(warning)
|
384
|
+
|
385
|
+
parsed_url = urlparse(api_url)
|
386
|
+
if parsed_url.path and not parsed_url.path.startswith("/api"):
|
387
|
+
warnings_list.append(
|
388
|
+
"`PREFECT_API_URL` should have `/api` after the base URL."
|
389
|
+
)
|
390
|
+
|
391
|
+
if warnings_list:
|
392
|
+
example = 'e.g. PREFECT_API_URL="https://api.prefect.cloud/api/accounts/[ACCOUNT-ID]/workspaces/[WORKSPACE-ID]"'
|
393
|
+
warnings_list.append(example)
|
394
|
+
|
395
|
+
warnings.warn("\n".join(warnings_list), stacklevel=2)
|
396
|
+
|
397
|
+
return settings
|
398
|
+
|
399
|
+
|
400
|
+
def _default_database_connection_url(settings: "Settings") -> SecretStr:
|
401
|
+
value = None
|
402
|
+
if settings.server.database.driver == "postgresql+asyncpg":
|
403
|
+
required = [
|
404
|
+
"host",
|
405
|
+
"user",
|
406
|
+
"name",
|
407
|
+
"password",
|
408
|
+
]
|
409
|
+
missing = [
|
410
|
+
attr for attr in required if getattr(settings.server.database, attr) is None
|
411
|
+
]
|
412
|
+
if missing:
|
413
|
+
raise ValueError(
|
414
|
+
f"Missing required database connection settings: {', '.join(missing)}"
|
415
|
+
)
|
416
|
+
|
417
|
+
from sqlalchemy import URL
|
418
|
+
|
419
|
+
return URL(
|
420
|
+
drivername=settings.server.database.driver,
|
421
|
+
host=settings.server.database.host,
|
422
|
+
port=settings.server.database.port or 5432,
|
423
|
+
username=settings.server.database.user,
|
424
|
+
password=(
|
425
|
+
settings.server.database.password.get_secret_value()
|
426
|
+
if settings.server.database.password
|
427
|
+
else None
|
428
|
+
),
|
429
|
+
database=settings.server.database.name,
|
430
|
+
query=[], # type: ignore
|
431
|
+
).render_as_string(hide_password=False)
|
432
|
+
|
433
|
+
elif settings.server.database.driver == "sqlite+aiosqlite":
|
434
|
+
if settings.server.database.name:
|
435
|
+
value = (
|
436
|
+
f"{settings.server.database.driver}:///{settings.server.database.name}"
|
437
|
+
)
|
438
|
+
else:
|
439
|
+
value = f"sqlite+aiosqlite:///{settings.home}/prefect.db"
|
440
|
+
|
441
|
+
elif settings.server.database.driver:
|
442
|
+
raise ValueError(
|
443
|
+
f"Unsupported database driver: {settings.server.database.driver}"
|
444
|
+
)
|
445
|
+
|
446
|
+
value = value if value else f"sqlite+aiosqlite:///{settings.home}/prefect.db"
|
447
|
+
return SecretStr(value)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
from pydantic import Field
|
2
|
+
from pydantic_settings import SettingsConfigDict
|
3
|
+
|
4
|
+
from prefect.settings.base import PrefectBaseSettings
|
5
|
+
from prefect.types import LogLevel
|
6
|
+
|
7
|
+
|
8
|
+
class RunnerServerSettings(PrefectBaseSettings):
|
9
|
+
"""
|
10
|
+
Settings for controlling runner server behavior
|
11
|
+
"""
|
12
|
+
|
13
|
+
model_config = SettingsConfigDict(
|
14
|
+
env_prefix="PREFECT_RUNNER_SERVER_", env_file=".env", extra="ignore"
|
15
|
+
)
|
16
|
+
|
17
|
+
enable: bool = Field(
|
18
|
+
default=False,
|
19
|
+
description="Whether or not to enable the runner's webserver.",
|
20
|
+
)
|
21
|
+
|
22
|
+
host: str = Field(
|
23
|
+
default="localhost",
|
24
|
+
description="The host address the runner's webserver should bind to.",
|
25
|
+
)
|
26
|
+
|
27
|
+
port: int = Field(
|
28
|
+
default=8080,
|
29
|
+
description="The port the runner's webserver should bind to.",
|
30
|
+
)
|
31
|
+
|
32
|
+
log_level: LogLevel = Field(
|
33
|
+
default="error",
|
34
|
+
description="The log level of the runner's webserver.",
|
35
|
+
)
|
36
|
+
|
37
|
+
missed_polls_tolerance: int = Field(
|
38
|
+
default=2,
|
39
|
+
description="Number of missed polls before a runner is considered unhealthy by its webserver.",
|
40
|
+
)
|
41
|
+
|
42
|
+
|
43
|
+
class RunnerSettings(PrefectBaseSettings):
|
44
|
+
"""
|
45
|
+
Settings for controlling runner behavior
|
46
|
+
"""
|
47
|
+
|
48
|
+
model_config = SettingsConfigDict(
|
49
|
+
env_prefix="PREFECT_RUNNER_", env_file=".env", extra="ignore"
|
50
|
+
)
|
51
|
+
|
52
|
+
process_limit: int = Field(
|
53
|
+
default=5,
|
54
|
+
description="Maximum number of processes a runner will execute in parallel.",
|
55
|
+
)
|
56
|
+
|
57
|
+
poll_frequency: int = Field(
|
58
|
+
default=10,
|
59
|
+
description="Number of seconds a runner should wait between queries for scheduled work.",
|
60
|
+
)
|
61
|
+
|
62
|
+
server: RunnerServerSettings = Field(
|
63
|
+
default_factory=RunnerServerSettings,
|
64
|
+
description="Settings for controlling runner server behavior",
|
65
|
+
)
|
@@ -0,0 +1 @@
|
|
1
|
+
from .root import ServerSettings
|
@@ -0,0 +1,133 @@
|
|
1
|
+
from datetime import timedelta
|
2
|
+
|
3
|
+
from pydantic import AliasChoices, AliasPath, Field
|
4
|
+
from pydantic_settings import SettingsConfigDict
|
5
|
+
|
6
|
+
from prefect.settings.base import PrefectBaseSettings
|
7
|
+
|
8
|
+
|
9
|
+
class ServerAPISettings(PrefectBaseSettings):
|
10
|
+
"""
|
11
|
+
Settings for controlling API server behavior
|
12
|
+
"""
|
13
|
+
|
14
|
+
model_config = SettingsConfigDict(
|
15
|
+
env_prefix="PREFECT_SERVER_API_", env_file=".env", extra="ignore"
|
16
|
+
)
|
17
|
+
|
18
|
+
host: str = Field(
|
19
|
+
default="127.0.0.1",
|
20
|
+
description="The API's host address (defaults to `127.0.0.1`).",
|
21
|
+
)
|
22
|
+
|
23
|
+
port: int = Field(
|
24
|
+
default=4200,
|
25
|
+
description="The API's port address (defaults to `4200`).",
|
26
|
+
)
|
27
|
+
|
28
|
+
default_limit: int = Field(
|
29
|
+
default=200,
|
30
|
+
description="The default limit applied to queries that can return multiple objects, such as `POST /flow_runs/filter`.",
|
31
|
+
validation_alias=AliasChoices(
|
32
|
+
AliasPath("default_limit"),
|
33
|
+
"prefect_server_api_default_limit",
|
34
|
+
"prefect_api_default_limit",
|
35
|
+
),
|
36
|
+
)
|
37
|
+
|
38
|
+
keepalive_timeout: int = Field(
|
39
|
+
default=5,
|
40
|
+
description="""
|
41
|
+
The API's keep alive timeout (defaults to `5`).
|
42
|
+
Refer to https://www.uvicorn.org/settings/#timeouts for details.
|
43
|
+
|
44
|
+
When the API is hosted behind a load balancer, you may want to set this to a value
|
45
|
+
greater than the load balancer's idle timeout.
|
46
|
+
|
47
|
+
Note this setting only applies when calling `prefect server start`; if hosting the
|
48
|
+
API with another tool you will need to configure this there instead.
|
49
|
+
""",
|
50
|
+
)
|
51
|
+
|
52
|
+
csrf_protection_enabled: bool = Field(
|
53
|
+
default=False,
|
54
|
+
description="""
|
55
|
+
Controls the activation of CSRF protection for the Prefect server API.
|
56
|
+
|
57
|
+
When enabled (`True`), the server enforces CSRF validation checks on incoming
|
58
|
+
state-changing requests (POST, PUT, PATCH, DELETE), requiring a valid CSRF
|
59
|
+
token to be included in the request headers or body. This adds a layer of
|
60
|
+
security by preventing unauthorized or malicious sites from making requests on
|
61
|
+
behalf of authenticated users.
|
62
|
+
|
63
|
+
It is recommended to enable this setting in production environments where the
|
64
|
+
API is exposed to web clients to safeguard against CSRF attacks.
|
65
|
+
|
66
|
+
Note: Enabling this setting requires corresponding support in the client for
|
67
|
+
CSRF token management. See PREFECT_CLIENT_CSRF_SUPPORT_ENABLED for more.
|
68
|
+
""",
|
69
|
+
validation_alias=AliasChoices(
|
70
|
+
AliasPath("csrf_protection_enabled"),
|
71
|
+
"prefect_server_api_csrf_protection_enabled",
|
72
|
+
"prefect_server_csrf_protection_enabled",
|
73
|
+
),
|
74
|
+
)
|
75
|
+
|
76
|
+
csrf_token_expiration: timedelta = Field(
|
77
|
+
default=timedelta(hours=1),
|
78
|
+
description="""
|
79
|
+
Specifies the duration for which a CSRF token remains valid after being issued
|
80
|
+
by the server.
|
81
|
+
|
82
|
+
The default expiration time is set to 1 hour, which offers a reasonable
|
83
|
+
compromise. Adjust this setting based on your specific security requirements
|
84
|
+
and usage patterns.
|
85
|
+
""",
|
86
|
+
validation_alias=AliasChoices(
|
87
|
+
AliasPath("csrf_token_expiration"),
|
88
|
+
"prefect_server_api_csrf_token_expiration",
|
89
|
+
"prefect_server_csrf_token_expiration",
|
90
|
+
),
|
91
|
+
)
|
92
|
+
|
93
|
+
cors_allowed_origins: str = Field(
|
94
|
+
default="*",
|
95
|
+
description="""
|
96
|
+
A comma-separated list of origins that are authorized to make cross-origin requests to the API.
|
97
|
+
|
98
|
+
By default, this is set to `*`, which allows requests from all origins.
|
99
|
+
""",
|
100
|
+
validation_alias=AliasChoices(
|
101
|
+
AliasPath("cors_allowed_origins"),
|
102
|
+
"prefect_server_api_cors_allowed_origins",
|
103
|
+
"prefect_server_cors_allowed_origins",
|
104
|
+
),
|
105
|
+
)
|
106
|
+
|
107
|
+
cors_allowed_methods: str = Field(
|
108
|
+
default="*",
|
109
|
+
description="""
|
110
|
+
A comma-separated list of methods that are authorized to make cross-origin requests to the API.
|
111
|
+
|
112
|
+
By default, this is set to `*`, which allows requests from all methods.
|
113
|
+
""",
|
114
|
+
validation_alias=AliasChoices(
|
115
|
+
AliasPath("cors_allowed_methods"),
|
116
|
+
"prefect_server_api_cors_allowed_methods",
|
117
|
+
"prefect_server_cors_allowed_methods",
|
118
|
+
),
|
119
|
+
)
|
120
|
+
|
121
|
+
cors_allowed_headers: str = Field(
|
122
|
+
default="*",
|
123
|
+
description="""
|
124
|
+
A comma-separated list of headers that are authorized to make cross-origin requests to the API.
|
125
|
+
|
126
|
+
By default, this is set to `*`, which allows requests from all headers.
|
127
|
+
""",
|
128
|
+
validation_alias=AliasChoices(
|
129
|
+
AliasPath("cors_allowed_headers"),
|
130
|
+
"prefect_server_api_cors_allowed_headers",
|
131
|
+
"prefect_server_cors_allowed_headers",
|
132
|
+
),
|
133
|
+
)
|