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,91 @@
|
|
1
|
+
from typing import Optional, Union
|
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 TasksRunnerSettings(PrefectBaseSettings):
|
10
|
+
model_config = SettingsConfigDict(
|
11
|
+
env_prefix="PREFECT_TASKS_RUNNER_", env_file=".env", extra="ignore"
|
12
|
+
)
|
13
|
+
|
14
|
+
thread_pool_max_workers: Optional[int] = Field(
|
15
|
+
default=None,
|
16
|
+
gt=0,
|
17
|
+
description="The maximum number of workers for ThreadPoolTaskRunner.",
|
18
|
+
validation_alias=AliasChoices(
|
19
|
+
AliasPath("thread_pool_max_workers"),
|
20
|
+
"prefect_tasks_runner_thread_pool_max_workers",
|
21
|
+
"prefect_task_runner_thread_pool_max_workers",
|
22
|
+
),
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
class TasksSchedulingSettings(PrefectBaseSettings):
|
27
|
+
model_config = SettingsConfigDict(
|
28
|
+
env_prefix="PREFECT_TASKS_SCHEDULING_", env_file=".env", extra="ignore"
|
29
|
+
)
|
30
|
+
|
31
|
+
default_storage_block: Optional[str] = Field(
|
32
|
+
default=None,
|
33
|
+
description="The `block-type/block-document` slug of a block to use as the default storage for autonomous tasks.",
|
34
|
+
validation_alias=AliasChoices(
|
35
|
+
AliasPath("default_storage_block"),
|
36
|
+
"prefect_tasks_scheduling_default_storage_block",
|
37
|
+
"prefect_task_scheduling_default_storage_block",
|
38
|
+
),
|
39
|
+
)
|
40
|
+
|
41
|
+
delete_failed_submissions: bool = Field(
|
42
|
+
default=True,
|
43
|
+
description="Whether or not to delete failed task submissions from the database.",
|
44
|
+
validation_alias=AliasChoices(
|
45
|
+
AliasPath("delete_failed_submissions"),
|
46
|
+
"prefect_tasks_scheduling_delete_failed_submissions",
|
47
|
+
"prefect_task_scheduling_delete_failed_submissions",
|
48
|
+
),
|
49
|
+
)
|
50
|
+
|
51
|
+
|
52
|
+
class TasksSettings(PrefectBaseSettings):
|
53
|
+
model_config = SettingsConfigDict(
|
54
|
+
env_prefix="PREFECT_TASKS_", env_file=".env", extra="ignore"
|
55
|
+
)
|
56
|
+
|
57
|
+
refresh_cache: bool = Field(
|
58
|
+
default=False,
|
59
|
+
description="If `True`, enables a refresh of cached results: re-executing the task will refresh the cached results.",
|
60
|
+
)
|
61
|
+
|
62
|
+
default_retries: int = Field(
|
63
|
+
default=0,
|
64
|
+
ge=0,
|
65
|
+
description="This value sets the default number of retries for all tasks.",
|
66
|
+
validation_alias=AliasChoices(
|
67
|
+
AliasPath("default_retries"),
|
68
|
+
"prefect_tasks_default_retries",
|
69
|
+
"prefect_task_default_retries",
|
70
|
+
),
|
71
|
+
)
|
72
|
+
|
73
|
+
default_retry_delay_seconds: Union[int, float, list[float]] = Field(
|
74
|
+
default=0,
|
75
|
+
description="This value sets the default retry delay seconds for all tasks.",
|
76
|
+
validation_alias=AliasChoices(
|
77
|
+
AliasPath("default_retry_delay_seconds"),
|
78
|
+
"prefect_tasks_default_retry_delay_seconds",
|
79
|
+
"prefect_task_default_retry_delay_seconds",
|
80
|
+
),
|
81
|
+
)
|
82
|
+
|
83
|
+
runner: TasksRunnerSettings = Field(
|
84
|
+
default_factory=TasksRunnerSettings,
|
85
|
+
description="Settings for controlling task runner behavior",
|
86
|
+
)
|
87
|
+
|
88
|
+
scheduling: TasksSchedulingSettings = Field(
|
89
|
+
default_factory=TasksSchedulingSettings,
|
90
|
+
description="Settings for controlling client-side task scheduling behavior",
|
91
|
+
)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
from typing import Any, Optional
|
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 TestingSettings(PrefectBaseSettings):
|
10
|
+
model_config = SettingsConfigDict(
|
11
|
+
env_prefix="PREFECT_TESTING_", env_file=".env", extra="ignore"
|
12
|
+
)
|
13
|
+
|
14
|
+
test_mode: bool = Field(
|
15
|
+
default=False,
|
16
|
+
description="If `True`, places the API in test mode. This may modify behavior to facilitate testing.",
|
17
|
+
validation_alias=AliasChoices(
|
18
|
+
AliasPath("test_mode"),
|
19
|
+
"prefect_testing_test_mode",
|
20
|
+
"prefect_test_mode",
|
21
|
+
),
|
22
|
+
)
|
23
|
+
|
24
|
+
unit_test_mode: bool = Field(
|
25
|
+
default=False,
|
26
|
+
description="This setting only exists to facilitate unit testing. If `True`, code is executing in a unit test context. Defaults to `False`.",
|
27
|
+
validation_alias=AliasChoices(
|
28
|
+
AliasPath("unit_test_mode"),
|
29
|
+
"prefect_testing_unit_test_mode",
|
30
|
+
"prefect_unit_test_mode",
|
31
|
+
),
|
32
|
+
)
|
33
|
+
|
34
|
+
unit_test_loop_debug: bool = Field(
|
35
|
+
default=True,
|
36
|
+
description="If `True` turns on debug mode for the unit testing event loop.",
|
37
|
+
validation_alias=AliasChoices(
|
38
|
+
AliasPath("unit_test_loop_debug"),
|
39
|
+
"prefect_testing_unit_test_loop_debug",
|
40
|
+
"prefect_unit_test_loop_debug",
|
41
|
+
),
|
42
|
+
)
|
43
|
+
|
44
|
+
test_setting: Optional[Any] = Field(
|
45
|
+
default="FOO",
|
46
|
+
description="This setting only exists to facilitate unit testing. If in test mode, this setting will return its value. Otherwise, it returns `None`.",
|
47
|
+
validation_alias=AliasChoices(
|
48
|
+
AliasPath("test_setting"),
|
49
|
+
"prefect_testing_test_setting",
|
50
|
+
"prefect_test_setting",
|
51
|
+
),
|
52
|
+
)
|
File without changes
|
@@ -0,0 +1,46 @@
|
|
1
|
+
from pydantic import Field
|
2
|
+
from pydantic_settings import SettingsConfigDict
|
3
|
+
|
4
|
+
from prefect.settings.base import PrefectBaseSettings
|
5
|
+
|
6
|
+
|
7
|
+
class WorkerWebserverSettings(PrefectBaseSettings):
|
8
|
+
model_config = SettingsConfigDict(
|
9
|
+
env_prefix="PREFECT_WORKER_WEBSERVER_", env_file=".env", extra="ignore"
|
10
|
+
)
|
11
|
+
|
12
|
+
host: str = Field(
|
13
|
+
default="0.0.0.0",
|
14
|
+
description="The host address the worker's webserver should bind to.",
|
15
|
+
)
|
16
|
+
|
17
|
+
port: int = Field(
|
18
|
+
default=8080,
|
19
|
+
description="The port the worker's webserver should bind to.",
|
20
|
+
)
|
21
|
+
|
22
|
+
|
23
|
+
class WorkerSettings(PrefectBaseSettings):
|
24
|
+
model_config = SettingsConfigDict(
|
25
|
+
env_prefix="PREFECT_WORKER_", env_file=".env", extra="ignore"
|
26
|
+
)
|
27
|
+
|
28
|
+
heartbeat_seconds: float = Field(
|
29
|
+
default=30,
|
30
|
+
description="Number of seconds a worker should wait between sending a heartbeat.",
|
31
|
+
)
|
32
|
+
|
33
|
+
query_seconds: float = Field(
|
34
|
+
default=10,
|
35
|
+
description="Number of seconds a worker should wait between queries for scheduled work.",
|
36
|
+
)
|
37
|
+
|
38
|
+
prefetch_seconds: float = Field(
|
39
|
+
default=10,
|
40
|
+
description="The number of seconds into the future a worker should query for scheduled work.",
|
41
|
+
)
|
42
|
+
|
43
|
+
webserver: WorkerWebserverSettings = Field(
|
44
|
+
default_factory=WorkerWebserverSettings,
|
45
|
+
description="Settings for a worker's webserver",
|
46
|
+
)
|
@@ -0,0 +1,390 @@
|
|
1
|
+
import inspect
|
2
|
+
import warnings
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Annotated, Any, Dict, Iterable, List, Optional, Set, Tuple, Union
|
5
|
+
|
6
|
+
import toml
|
7
|
+
from pydantic import (
|
8
|
+
BaseModel,
|
9
|
+
BeforeValidator,
|
10
|
+
ConfigDict,
|
11
|
+
Field,
|
12
|
+
TypeAdapter,
|
13
|
+
ValidationError,
|
14
|
+
)
|
15
|
+
from pydantic_settings import BaseSettings
|
16
|
+
|
17
|
+
from prefect.exceptions import ProfileSettingsValidationError
|
18
|
+
from prefect.settings.constants import DEFAULT_PROFILES_PATH
|
19
|
+
from prefect.settings.context import get_current_settings
|
20
|
+
from prefect.settings.legacy import Setting, _get_settings_fields
|
21
|
+
from prefect.settings.models.root import Settings
|
22
|
+
|
23
|
+
|
24
|
+
def _cast_settings(
|
25
|
+
settings: Union[Dict[Union[str, Setting], Any], Any],
|
26
|
+
) -> Dict[Setting, Any]:
|
27
|
+
"""For backwards compatibility, allow either Settings objects as keys or string references to settings."""
|
28
|
+
if not isinstance(settings, dict):
|
29
|
+
raise ValueError("Settings must be a dictionary.")
|
30
|
+
casted_settings = {}
|
31
|
+
for k, value in settings.items():
|
32
|
+
try:
|
33
|
+
if isinstance(k, str):
|
34
|
+
setting = _get_settings_fields(Settings)[k]
|
35
|
+
else:
|
36
|
+
setting = k
|
37
|
+
casted_settings[setting] = value
|
38
|
+
except KeyError as e:
|
39
|
+
warnings.warn(f"Setting {e} is not recognized")
|
40
|
+
continue
|
41
|
+
return casted_settings
|
42
|
+
|
43
|
+
|
44
|
+
############################################################################
|
45
|
+
# Profiles
|
46
|
+
|
47
|
+
|
48
|
+
class Profile(BaseModel):
|
49
|
+
"""A user profile containing settings."""
|
50
|
+
|
51
|
+
model_config = ConfigDict(extra="ignore", arbitrary_types_allowed=True)
|
52
|
+
|
53
|
+
name: str
|
54
|
+
settings: Annotated[Dict[Setting, Any], BeforeValidator(_cast_settings)] = Field(
|
55
|
+
default_factory=dict
|
56
|
+
)
|
57
|
+
source: Optional[Path] = None
|
58
|
+
|
59
|
+
def to_environment_variables(self) -> Dict[str, str]:
|
60
|
+
"""Convert the profile settings to a dictionary of environment variables."""
|
61
|
+
return {
|
62
|
+
setting.name: str(value)
|
63
|
+
for setting, value in self.settings.items()
|
64
|
+
if value is not None
|
65
|
+
}
|
66
|
+
|
67
|
+
def validate_settings(self):
|
68
|
+
errors: List[Tuple[Setting, ValidationError]] = []
|
69
|
+
for setting, value in self.settings.items():
|
70
|
+
try:
|
71
|
+
model_fields = Settings.model_fields
|
72
|
+
annotation = None
|
73
|
+
for section in setting.accessor.split("."):
|
74
|
+
annotation = model_fields[section].annotation
|
75
|
+
if inspect.isclass(annotation) and issubclass(
|
76
|
+
annotation, BaseSettings
|
77
|
+
):
|
78
|
+
model_fields = annotation.model_fields
|
79
|
+
|
80
|
+
TypeAdapter(annotation).validate_python(value)
|
81
|
+
except ValidationError as e:
|
82
|
+
errors.append((setting, e))
|
83
|
+
if errors:
|
84
|
+
raise ProfileSettingsValidationError(errors)
|
85
|
+
|
86
|
+
|
87
|
+
class ProfilesCollection:
|
88
|
+
""" "
|
89
|
+
A utility class for working with a collection of profiles.
|
90
|
+
|
91
|
+
Profiles in the collection must have unique names.
|
92
|
+
|
93
|
+
The collection may store the name of the active profile.
|
94
|
+
"""
|
95
|
+
|
96
|
+
def __init__(
|
97
|
+
self, profiles: Iterable[Profile], active: Optional[str] = None
|
98
|
+
) -> None:
|
99
|
+
self.profiles_by_name = {profile.name: profile for profile in profiles}
|
100
|
+
self.active_name = active
|
101
|
+
|
102
|
+
@property
|
103
|
+
def names(self) -> Set[str]:
|
104
|
+
"""
|
105
|
+
Return a set of profile names in this collection.
|
106
|
+
"""
|
107
|
+
return set(self.profiles_by_name.keys())
|
108
|
+
|
109
|
+
@property
|
110
|
+
def active_profile(self) -> Optional[Profile]:
|
111
|
+
"""
|
112
|
+
Retrieve the active profile in this collection.
|
113
|
+
"""
|
114
|
+
if self.active_name is None:
|
115
|
+
return None
|
116
|
+
return self[self.active_name]
|
117
|
+
|
118
|
+
def set_active(self, name: Optional[str], check: bool = True):
|
119
|
+
"""
|
120
|
+
Set the active profile name in the collection.
|
121
|
+
|
122
|
+
A null value may be passed to indicate that this collection does not determine
|
123
|
+
the active profile.
|
124
|
+
"""
|
125
|
+
if check and name is not None and name not in self.names:
|
126
|
+
raise ValueError(f"Unknown profile name {name!r}.")
|
127
|
+
self.active_name = name
|
128
|
+
|
129
|
+
def update_profile(
|
130
|
+
self,
|
131
|
+
name: str,
|
132
|
+
settings: Dict[Setting, Any],
|
133
|
+
source: Optional[Path] = None,
|
134
|
+
) -> Profile:
|
135
|
+
"""
|
136
|
+
Add a profile to the collection or update the existing on if the name is already
|
137
|
+
present in this collection.
|
138
|
+
|
139
|
+
If updating an existing profile, the settings will be merged. Settings can
|
140
|
+
be dropped from the existing profile by setting them to `None` in the new
|
141
|
+
profile.
|
142
|
+
|
143
|
+
Returns the new profile object.
|
144
|
+
"""
|
145
|
+
existing = self.profiles_by_name.get(name)
|
146
|
+
|
147
|
+
# Convert the input to a `Profile` to cast settings to the correct type
|
148
|
+
profile = Profile(name=name, settings=settings, source=source)
|
149
|
+
|
150
|
+
if existing:
|
151
|
+
new_settings = {**existing.settings, **profile.settings}
|
152
|
+
|
153
|
+
# Drop null keys to restore to default
|
154
|
+
for key, value in tuple(new_settings.items()):
|
155
|
+
if value is None:
|
156
|
+
new_settings.pop(key)
|
157
|
+
|
158
|
+
new_profile = Profile(
|
159
|
+
name=profile.name,
|
160
|
+
settings=new_settings,
|
161
|
+
source=source or profile.source,
|
162
|
+
)
|
163
|
+
else:
|
164
|
+
new_profile = profile
|
165
|
+
|
166
|
+
self.profiles_by_name[new_profile.name] = new_profile
|
167
|
+
|
168
|
+
return new_profile
|
169
|
+
|
170
|
+
def add_profile(self, profile: Profile) -> None:
|
171
|
+
"""
|
172
|
+
Add a profile to the collection.
|
173
|
+
|
174
|
+
If the profile name already exists, an exception will be raised.
|
175
|
+
"""
|
176
|
+
if profile.name in self.profiles_by_name:
|
177
|
+
raise ValueError(
|
178
|
+
f"Profile name {profile.name!r} already exists in collection."
|
179
|
+
)
|
180
|
+
|
181
|
+
self.profiles_by_name[profile.name] = profile
|
182
|
+
|
183
|
+
def remove_profile(self, name: str) -> None:
|
184
|
+
"""
|
185
|
+
Remove a profile from the collection.
|
186
|
+
"""
|
187
|
+
self.profiles_by_name.pop(name)
|
188
|
+
|
189
|
+
def without_profile_source(self, path: Optional[Path]) -> "ProfilesCollection":
|
190
|
+
"""
|
191
|
+
Remove profiles that were loaded from a given path.
|
192
|
+
|
193
|
+
Returns a new collection.
|
194
|
+
"""
|
195
|
+
return ProfilesCollection(
|
196
|
+
[
|
197
|
+
profile
|
198
|
+
for profile in self.profiles_by_name.values()
|
199
|
+
if profile.source != path
|
200
|
+
],
|
201
|
+
active=self.active_name,
|
202
|
+
)
|
203
|
+
|
204
|
+
def to_dict(self):
|
205
|
+
"""
|
206
|
+
Convert to a dictionary suitable for writing to disk.
|
207
|
+
"""
|
208
|
+
return {
|
209
|
+
"active": self.active_name,
|
210
|
+
"profiles": {
|
211
|
+
profile.name: profile.to_environment_variables()
|
212
|
+
for profile in self.profiles_by_name.values()
|
213
|
+
},
|
214
|
+
}
|
215
|
+
|
216
|
+
def __getitem__(self, name: str) -> Profile:
|
217
|
+
return self.profiles_by_name[name]
|
218
|
+
|
219
|
+
def __iter__(self):
|
220
|
+
return self.profiles_by_name.__iter__()
|
221
|
+
|
222
|
+
def items(self):
|
223
|
+
return self.profiles_by_name.items()
|
224
|
+
|
225
|
+
def __eq__(self, __o: object) -> bool:
|
226
|
+
if not isinstance(__o, ProfilesCollection):
|
227
|
+
return False
|
228
|
+
|
229
|
+
return (
|
230
|
+
self.profiles_by_name == __o.profiles_by_name
|
231
|
+
and self.active_name == __o.active_name
|
232
|
+
)
|
233
|
+
|
234
|
+
def __repr__(self) -> str:
|
235
|
+
return (
|
236
|
+
f"ProfilesCollection(profiles={list(self.profiles_by_name.values())!r},"
|
237
|
+
f" active={self.active_name!r})>"
|
238
|
+
)
|
239
|
+
|
240
|
+
|
241
|
+
def _read_profiles_from(path: Path) -> ProfilesCollection:
|
242
|
+
"""
|
243
|
+
Read profiles from a path into a new `ProfilesCollection`.
|
244
|
+
|
245
|
+
Profiles are expected to be written in TOML with the following schema:
|
246
|
+
```
|
247
|
+
active = <name: Optional[str]>
|
248
|
+
|
249
|
+
[profiles.<name: str>]
|
250
|
+
<SETTING: str> = <value: Any>
|
251
|
+
```
|
252
|
+
"""
|
253
|
+
contents = toml.loads(path.read_text())
|
254
|
+
active_profile = contents.get("active")
|
255
|
+
raw_profiles = contents.get("profiles", {})
|
256
|
+
|
257
|
+
profiles = []
|
258
|
+
for name, settings in raw_profiles.items():
|
259
|
+
profiles.append(Profile(name=name, settings=settings, source=path))
|
260
|
+
|
261
|
+
return ProfilesCollection(profiles, active=active_profile)
|
262
|
+
|
263
|
+
|
264
|
+
def _write_profiles_to(path: Path, profiles: ProfilesCollection) -> None:
|
265
|
+
"""
|
266
|
+
Write profiles in the given collection to a path as TOML.
|
267
|
+
|
268
|
+
Any existing data not present in the given `profiles` will be deleted.
|
269
|
+
"""
|
270
|
+
if not path.exists():
|
271
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
272
|
+
path.touch(mode=0o600)
|
273
|
+
path.write_text(toml.dumps(profiles.to_dict()))
|
274
|
+
|
275
|
+
|
276
|
+
def load_profiles(include_defaults: bool = True) -> ProfilesCollection:
|
277
|
+
"""
|
278
|
+
Load profiles from the current profile path. Optionally include profiles from the
|
279
|
+
default profile path.
|
280
|
+
"""
|
281
|
+
current_settings = get_current_settings()
|
282
|
+
default_profiles = _read_profiles_from(DEFAULT_PROFILES_PATH)
|
283
|
+
|
284
|
+
if current_settings.profiles_path is None:
|
285
|
+
raise RuntimeError(
|
286
|
+
"No profiles path set; please ensure `PREFECT_PROFILES_PATH` is set."
|
287
|
+
)
|
288
|
+
|
289
|
+
if not include_defaults:
|
290
|
+
if not current_settings.profiles_path.exists():
|
291
|
+
return ProfilesCollection([])
|
292
|
+
return _read_profiles_from(current_settings.profiles_path)
|
293
|
+
|
294
|
+
user_profiles_path = current_settings.profiles_path
|
295
|
+
profiles = default_profiles
|
296
|
+
if user_profiles_path.exists():
|
297
|
+
user_profiles = _read_profiles_from(user_profiles_path)
|
298
|
+
|
299
|
+
# Merge all of the user profiles with the defaults
|
300
|
+
for name in user_profiles:
|
301
|
+
if not (source := user_profiles[name].source):
|
302
|
+
raise ValueError(f"Profile {name!r} has no source.")
|
303
|
+
profiles.update_profile(
|
304
|
+
name,
|
305
|
+
settings=user_profiles[name].settings,
|
306
|
+
source=source,
|
307
|
+
)
|
308
|
+
|
309
|
+
if user_profiles.active_name:
|
310
|
+
profiles.set_active(user_profiles.active_name, check=False)
|
311
|
+
|
312
|
+
return profiles
|
313
|
+
|
314
|
+
|
315
|
+
def load_current_profile():
|
316
|
+
"""
|
317
|
+
Load the current profile from the default and current profile paths.
|
318
|
+
|
319
|
+
This will _not_ include settings from the current settings context. Only settings
|
320
|
+
that have been persisted to the profiles file will be saved.
|
321
|
+
"""
|
322
|
+
import prefect.context
|
323
|
+
|
324
|
+
profiles = load_profiles()
|
325
|
+
context = prefect.context.get_settings_context()
|
326
|
+
|
327
|
+
if context:
|
328
|
+
profiles.set_active(context.profile.name)
|
329
|
+
|
330
|
+
return profiles.active_profile
|
331
|
+
|
332
|
+
|
333
|
+
def save_profiles(profiles: ProfilesCollection) -> None:
|
334
|
+
"""
|
335
|
+
Writes all non-default profiles to the current profiles path.
|
336
|
+
"""
|
337
|
+
profiles_path = get_current_settings().profiles_path
|
338
|
+
assert profiles_path is not None, "Profiles path is not set."
|
339
|
+
profiles = profiles.without_profile_source(DEFAULT_PROFILES_PATH)
|
340
|
+
return _write_profiles_to(profiles_path, profiles)
|
341
|
+
|
342
|
+
|
343
|
+
def load_profile(name: str) -> Profile:
|
344
|
+
"""
|
345
|
+
Load a single profile by name.
|
346
|
+
"""
|
347
|
+
profiles = load_profiles()
|
348
|
+
try:
|
349
|
+
return profiles[name]
|
350
|
+
except KeyError:
|
351
|
+
raise ValueError(f"Profile {name!r} not found.")
|
352
|
+
|
353
|
+
|
354
|
+
def update_current_profile(
|
355
|
+
settings: Dict[Union[str, Setting], Any],
|
356
|
+
) -> Profile:
|
357
|
+
"""
|
358
|
+
Update the persisted data for the profile currently in-use.
|
359
|
+
|
360
|
+
If the profile does not exist in the profiles file, it will be created.
|
361
|
+
|
362
|
+
Given settings will be merged with the existing settings as described in
|
363
|
+
`ProfilesCollection.update_profile`.
|
364
|
+
|
365
|
+
Returns:
|
366
|
+
The new profile.
|
367
|
+
"""
|
368
|
+
import prefect.context
|
369
|
+
|
370
|
+
current_profile = prefect.context.get_settings_context().profile
|
371
|
+
|
372
|
+
if not current_profile:
|
373
|
+
from prefect.exceptions import MissingProfileError
|
374
|
+
|
375
|
+
raise MissingProfileError("No profile is currently in use.")
|
376
|
+
|
377
|
+
profiles = load_profiles()
|
378
|
+
|
379
|
+
# Ensure the current profile's settings are present
|
380
|
+
profiles.update_profile(current_profile.name, current_profile.settings)
|
381
|
+
# Then merge the new settings in
|
382
|
+
new_profile = profiles.update_profile(
|
383
|
+
current_profile.name, _cast_settings(settings)
|
384
|
+
)
|
385
|
+
|
386
|
+
new_profile.validate_settings()
|
387
|
+
|
388
|
+
save_profiles(profiles)
|
389
|
+
|
390
|
+
return profiles[current_profile.name]
|