prefect-client 2.17.1__py3-none-any.whl → 2.18.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prefect/_internal/compatibility/deprecated.py +2 -0
- prefect/_internal/pydantic/_compat.py +1 -0
- prefect/_internal/pydantic/utilities/field_validator.py +25 -10
- prefect/_internal/pydantic/utilities/model_dump.py +1 -1
- prefect/_internal/pydantic/utilities/model_validate.py +1 -1
- prefect/_internal/pydantic/utilities/model_validator.py +11 -3
- prefect/_internal/schemas/fields.py +31 -12
- prefect/_internal/schemas/validators.py +0 -6
- prefect/_version.py +97 -38
- prefect/blocks/abstract.py +34 -1
- prefect/blocks/core.py +1 -1
- prefect/blocks/notifications.py +16 -7
- prefect/blocks/system.py +2 -3
- prefect/client/base.py +10 -5
- prefect/client/orchestration.py +405 -85
- prefect/client/schemas/actions.py +4 -3
- prefect/client/schemas/objects.py +6 -5
- prefect/client/schemas/schedules.py +2 -6
- prefect/client/schemas/sorting.py +9 -0
- prefect/client/utilities.py +25 -3
- prefect/concurrency/asyncio.py +11 -5
- prefect/concurrency/events.py +3 -3
- prefect/concurrency/services.py +1 -1
- prefect/concurrency/sync.py +9 -5
- prefect/deployments/__init__.py +0 -2
- prefect/deployments/base.py +2 -144
- prefect/deployments/deployments.py +29 -20
- prefect/deployments/runner.py +36 -28
- prefect/deployments/steps/core.py +3 -3
- prefect/deprecated/packaging/serializers.py +5 -4
- prefect/engine.py +3 -1
- prefect/events/__init__.py +45 -0
- prefect/events/actions.py +250 -18
- prefect/events/cli/automations.py +201 -0
- prefect/events/clients.py +179 -21
- prefect/events/filters.py +30 -3
- prefect/events/instrument.py +40 -40
- prefect/events/related.py +2 -1
- prefect/events/schemas/automations.py +126 -8
- prefect/events/schemas/deployment_triggers.py +23 -277
- prefect/events/schemas/events.py +7 -7
- prefect/events/utilities.py +3 -1
- prefect/events/worker.py +21 -8
- prefect/exceptions.py +1 -1
- prefect/flows.py +33 -18
- prefect/input/actions.py +9 -9
- prefect/input/run_input.py +49 -37
- prefect/logging/__init__.py +2 -2
- prefect/logging/loggers.py +64 -1
- prefect/new_flow_engine.py +293 -0
- prefect/new_task_engine.py +374 -0
- prefect/results.py +32 -12
- prefect/runner/runner.py +3 -2
- prefect/serializers.py +62 -31
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +44 -3
- prefect/settings.py +32 -10
- prefect/states.py +25 -19
- prefect/tasks.py +17 -0
- prefect/types/__init__.py +90 -0
- prefect/utilities/asyncutils.py +37 -0
- prefect/utilities/engine.py +6 -4
- prefect/utilities/pydantic.py +34 -15
- prefect/utilities/schema_tools/hydration.py +88 -19
- prefect/utilities/schema_tools/validation.py +1 -1
- prefect/variables.py +4 -4
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/METADATA +1 -1
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/RECORD +71 -67
- /prefect/{concurrency/common.py → events/cli/__init__.py} +0 -0
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/LICENSE +0 -0
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/WHEEL +0 -0
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import Any, Callable, ClassVar, Generator
|
3
|
+
|
4
|
+
from pydantic_core import core_schema, CoreSchema, SchemaValidator
|
5
|
+
from typing_extensions import Self
|
6
|
+
from datetime import timedelta
|
7
|
+
|
8
|
+
|
9
|
+
@dataclass
|
10
|
+
class NonNegativeInteger(int):
|
11
|
+
schema: ClassVar[CoreSchema] = core_schema.int_schema(ge=0)
|
12
|
+
|
13
|
+
@classmethod
|
14
|
+
def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]:
|
15
|
+
yield cls.validate
|
16
|
+
|
17
|
+
@classmethod
|
18
|
+
def __get_pydantic_core_schema__(
|
19
|
+
cls, source_type: Any, handler: Callable[..., Any]
|
20
|
+
) -> CoreSchema:
|
21
|
+
return cls.schema
|
22
|
+
|
23
|
+
@classmethod
|
24
|
+
def validate(cls, v: Any) -> Self:
|
25
|
+
return SchemaValidator(schema=cls.schema).validate_python(v)
|
26
|
+
|
27
|
+
|
28
|
+
@dataclass
|
29
|
+
class PositiveInteger(int):
|
30
|
+
schema: ClassVar[CoreSchema] = core_schema.int_schema(gt=0)
|
31
|
+
|
32
|
+
@classmethod
|
33
|
+
def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]:
|
34
|
+
yield cls.validate
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
def __get_pydantic_core_schema__(
|
38
|
+
cls, source_type: Any, handler: Callable[..., Any]
|
39
|
+
) -> CoreSchema:
|
40
|
+
return cls.schema
|
41
|
+
|
42
|
+
@classmethod
|
43
|
+
def validate(cls, v: Any) -> Self:
|
44
|
+
return SchemaValidator(schema=cls.schema).validate_python(v)
|
45
|
+
|
46
|
+
|
47
|
+
@dataclass
|
48
|
+
class NonNegativeDuration(timedelta):
|
49
|
+
schema: ClassVar = core_schema.timedelta_schema(ge=timedelta(seconds=0))
|
50
|
+
|
51
|
+
@classmethod
|
52
|
+
def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]:
|
53
|
+
yield cls.validate
|
54
|
+
|
55
|
+
@classmethod
|
56
|
+
def __get_pydantic_core_schema__(
|
57
|
+
cls, source_type: Any, handler: Callable[..., Any]
|
58
|
+
) -> CoreSchema:
|
59
|
+
return cls.schema
|
60
|
+
|
61
|
+
@classmethod
|
62
|
+
def validate(cls, v: Any) -> Self:
|
63
|
+
return SchemaValidator(schema=cls.schema).validate_python(v)
|
64
|
+
|
65
|
+
|
66
|
+
@dataclass
|
67
|
+
class PositiveDuration(timedelta):
|
68
|
+
schema: ClassVar = core_schema.timedelta_schema(gt=timedelta(seconds=0))
|
69
|
+
|
70
|
+
@classmethod
|
71
|
+
def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]:
|
72
|
+
yield cls.validate
|
73
|
+
|
74
|
+
@classmethod
|
75
|
+
def __get_pydantic_core_schema__(
|
76
|
+
cls, source_type: Any, handler: Callable[..., Any]
|
77
|
+
) -> CoreSchema:
|
78
|
+
return cls.schema
|
79
|
+
|
80
|
+
@classmethod
|
81
|
+
def validate(cls, v: Any) -> Self:
|
82
|
+
return SchemaValidator(schema=cls.schema).validate_python(v)
|
83
|
+
|
84
|
+
|
85
|
+
__all__ = [
|
86
|
+
"NonNegativeInteger",
|
87
|
+
"PositiveInteger",
|
88
|
+
"NonNegativeDuration",
|
89
|
+
"PositiveDuration",
|
90
|
+
]
|
prefect/utilities/asyncutils.py
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
"""
|
2
2
|
Utilities for interoperability with async functions and workers from various contexts.
|
3
3
|
"""
|
4
|
+
|
4
5
|
import asyncio
|
5
6
|
import ctypes
|
6
7
|
import inspect
|
7
8
|
import threading
|
8
9
|
import warnings
|
10
|
+
from concurrent.futures import ThreadPoolExecutor
|
9
11
|
from contextlib import asynccontextmanager
|
10
12
|
from functools import partial, wraps
|
11
13
|
from threading import Thread
|
@@ -78,6 +80,41 @@ def is_async_gen_fn(func):
|
|
78
80
|
return inspect.isasyncgenfunction(func)
|
79
81
|
|
80
82
|
|
83
|
+
def run_sync(coroutine: Coroutine[Any, Any, T]) -> T:
|
84
|
+
"""
|
85
|
+
Runs a coroutine from a synchronous context, either in the current event
|
86
|
+
loop or in a new one if there is no event loop running. The coroutine will
|
87
|
+
block until it is done. A thread will be spawned to run the event loop if
|
88
|
+
necessary, which allows coroutines to run in environments like Jupyter
|
89
|
+
notebooks where the event loop runs on the main thread.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
coroutine: The coroutine to run.
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
The return value of the coroutine.
|
96
|
+
|
97
|
+
Example:
|
98
|
+
Basic usage:
|
99
|
+
```python
|
100
|
+
async def my_async_function(x: int) -> int:
|
101
|
+
return x + 1
|
102
|
+
|
103
|
+
run_sync(my_async_function(1))
|
104
|
+
```
|
105
|
+
"""
|
106
|
+
try:
|
107
|
+
loop = asyncio.get_running_loop()
|
108
|
+
if loop.is_running():
|
109
|
+
with ThreadPoolExecutor() as executor:
|
110
|
+
future = executor.submit(asyncio.run, coroutine)
|
111
|
+
return future.result()
|
112
|
+
else:
|
113
|
+
return asyncio.run(coroutine)
|
114
|
+
except RuntimeError:
|
115
|
+
return asyncio.run(coroutine)
|
116
|
+
|
117
|
+
|
81
118
|
async def run_sync_in_worker_thread(
|
82
119
|
__fn: Callable[..., T], *args: Any, **kwargs: Any
|
83
120
|
) -> T:
|
prefect/utilities/engine.py
CHANGED
@@ -11,6 +11,7 @@ from typing import (
|
|
11
11
|
Iterable,
|
12
12
|
Optional,
|
13
13
|
Set,
|
14
|
+
TypeVar,
|
14
15
|
Union,
|
15
16
|
)
|
16
17
|
from uuid import UUID, uuid4
|
@@ -66,6 +67,7 @@ from prefect.utilities.text import truncated_to
|
|
66
67
|
API_HEALTHCHECKS = {}
|
67
68
|
UNTRACKABLE_TYPES = {bool, type(None), type(...), type(NotImplemented)}
|
68
69
|
engine_logger = get_logger("engine")
|
70
|
+
T = TypeVar("T")
|
69
71
|
|
70
72
|
|
71
73
|
async def collect_task_run_inputs(expr: Any, max_depth: int = -1) -> Set[TaskRunInput]:
|
@@ -308,11 +310,11 @@ async def resolve_inputs(
|
|
308
310
|
|
309
311
|
async def propose_state(
|
310
312
|
client: PrefectClient,
|
311
|
-
state: State,
|
313
|
+
state: State[object],
|
312
314
|
force: bool = False,
|
313
|
-
task_run_id: UUID = None,
|
314
|
-
flow_run_id: UUID = None,
|
315
|
-
) -> State:
|
315
|
+
task_run_id: Optional[UUID] = None,
|
316
|
+
flow_run_id: Optional[UUID] = None,
|
317
|
+
) -> State[object]:
|
316
318
|
"""
|
317
319
|
Propose a new state for a flow run or task run, invoking Prefect orchestration logic.
|
318
320
|
|
prefect/utilities/pydantic.py
CHANGED
@@ -1,24 +1,25 @@
|
|
1
1
|
from functools import partial
|
2
|
-
from typing import Any, Callable, Generic, Type, TypeVar, cast, overload
|
3
|
-
|
4
|
-
from prefect._internal.pydantic import HAS_PYDANTIC_V2
|
5
|
-
|
6
|
-
if HAS_PYDANTIC_V2:
|
7
|
-
import pydantic.v1 as pydantic
|
8
|
-
else:
|
9
|
-
import pydantic
|
2
|
+
from typing import Any, Callable, Dict, Generic, Optional, Type, TypeVar, cast, overload
|
10
3
|
|
11
4
|
from jsonpatch import JsonPatch as JsonPatchBase
|
5
|
+
from pydantic_core import to_jsonable_python
|
12
6
|
from typing_extensions import Self
|
13
7
|
|
8
|
+
from prefect._internal.pydantic.utilities.model_dump import model_dump
|
9
|
+
from prefect.pydantic import HAS_PYDANTIC_V2
|
14
10
|
from prefect.utilities.dispatch import get_dispatch_key, lookup_type, register_base_type
|
15
11
|
from prefect.utilities.importtools import from_qualified_name, to_qualified_name
|
16
12
|
|
13
|
+
if HAS_PYDANTIC_V2:
|
14
|
+
import pydantic.v1 as pydantic_v1
|
15
|
+
else:
|
16
|
+
import pydantic as pydantic_v1
|
17
|
+
|
17
18
|
D = TypeVar("D", bound=Any)
|
18
|
-
M = TypeVar("M", bound=
|
19
|
+
M = TypeVar("M", bound=pydantic_v1.BaseModel)
|
19
20
|
|
20
21
|
|
21
|
-
def _reduce_model(model:
|
22
|
+
def _reduce_model(model: pydantic_v1.BaseModel):
|
22
23
|
"""
|
23
24
|
Helper for serializing a cythonized model with cloudpickle.
|
24
25
|
|
@@ -82,7 +83,7 @@ def add_cloudpickle_reduction(__model_cls: Type[M] = None, **kwargs: Any):
|
|
82
83
|
)
|
83
84
|
|
84
85
|
|
85
|
-
def get_class_fields_only(model: Type[
|
86
|
+
def get_class_fields_only(model: Type[pydantic_v1.BaseModel]) -> set:
|
86
87
|
"""
|
87
88
|
Gets all the field names defined on the model class but not any parent classes.
|
88
89
|
Any fields that are on the parent but redefined on the subclass are included.
|
@@ -91,7 +92,7 @@ def get_class_fields_only(model: Type[pydantic.BaseModel]) -> set:
|
|
91
92
|
parent_class_fields = set()
|
92
93
|
|
93
94
|
for base in model.__class__.__bases__:
|
94
|
-
if issubclass(base,
|
95
|
+
if issubclass(base, pydantic_v1.BaseModel):
|
95
96
|
parent_class_fields.update(base.__annotations__.keys())
|
96
97
|
|
97
98
|
return (subclass_class_fields - parent_class_fields) | (
|
@@ -134,7 +135,7 @@ def add_type_dispatch(model_cls: Type[M]) -> Type[M]:
|
|
134
135
|
|
135
136
|
elif defines_dispatch_key and not defines_type_field:
|
136
137
|
# Add a type field to store the value of the dispatch key
|
137
|
-
model_cls.__fields__["type"] =
|
138
|
+
model_cls.__fields__["type"] = pydantic_v1.fields.ModelField(
|
138
139
|
name="type",
|
139
140
|
type_=str,
|
140
141
|
required=True,
|
@@ -180,7 +181,7 @@ def add_type_dispatch(model_cls: Type[M]) -> Type[M]:
|
|
180
181
|
try:
|
181
182
|
subcls = lookup_type(cls, dispatch_key=kwargs["type"])
|
182
183
|
except KeyError as exc:
|
183
|
-
raise
|
184
|
+
raise pydantic_v1.ValidationError(errors=[exc], model=cls)
|
184
185
|
return cls_new(subcls)
|
185
186
|
else:
|
186
187
|
return cls_new(cls)
|
@@ -206,7 +207,7 @@ class PartialModel(Generic[M]):
|
|
206
207
|
a field already has a value.
|
207
208
|
|
208
209
|
Example:
|
209
|
-
>>> class MyModel(
|
210
|
+
>>> class MyModel(pydantic_v1.BaseModel):
|
210
211
|
>>> x: int
|
211
212
|
>>> y: str
|
212
213
|
>>> z: float
|
@@ -267,3 +268,21 @@ class JsonPatch(JsonPatchBase):
|
|
267
268
|
},
|
268
269
|
}
|
269
270
|
)
|
271
|
+
|
272
|
+
|
273
|
+
def custom_pydantic_encoder(
|
274
|
+
type_encoders: Optional[Dict[Any, Callable[[Type[Any]], Any]]], obj: Any
|
275
|
+
) -> Any:
|
276
|
+
# Check the class type and its superclasses for a matching encoder
|
277
|
+
for base in obj.__class__.__mro__[:-1]:
|
278
|
+
try:
|
279
|
+
encoder = type_encoders[base]
|
280
|
+
except KeyError:
|
281
|
+
continue
|
282
|
+
|
283
|
+
return encoder(obj)
|
284
|
+
else: # We have exited the for loop without finding a suitable encoder
|
285
|
+
if isinstance(obj, pydantic_v1.BaseModel):
|
286
|
+
return model_dump(obj, mode="json")
|
287
|
+
else:
|
288
|
+
return to_jsonable_python(obj)
|
@@ -1,41 +1,53 @@
|
|
1
1
|
import json
|
2
2
|
from typing import Any, Callable, Dict, Optional
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
if HAS_PYDANTIC_V2:
|
7
|
-
from pydantic.v1 import BaseModel, Field
|
8
|
-
else:
|
9
|
-
from pydantic import BaseModel, Field
|
10
|
-
|
4
|
+
import jinja2
|
5
|
+
from pydantic import BaseModel, Field
|
11
6
|
from sqlalchemy.ext.asyncio import AsyncSession
|
12
7
|
from typing_extensions import TypeAlias
|
13
8
|
|
14
|
-
from prefect.server.
|
9
|
+
from prefect.server.utilities.user_templates import (
|
10
|
+
TemplateSecurityError,
|
11
|
+
render_user_template_sync,
|
12
|
+
validate_user_template,
|
13
|
+
)
|
15
14
|
|
16
15
|
|
17
16
|
class HydrationContext(BaseModel):
|
18
17
|
workspace_variables: Dict[str, str] = Field(default_factory=dict)
|
18
|
+
render_workspace_variables: bool = Field(default=False)
|
19
19
|
raise_on_error: bool = Field(default=False)
|
20
|
+
render_jinja: bool = Field(default=False)
|
21
|
+
jinja_context: Dict[str, Any] = Field(default_factory=dict)
|
20
22
|
|
21
23
|
@classmethod
|
22
24
|
async def build(
|
23
25
|
cls,
|
24
26
|
session: AsyncSession,
|
25
27
|
raise_on_error: bool = False,
|
28
|
+
render_jinja: bool = False,
|
29
|
+
render_workspace_variables: bool = False,
|
26
30
|
) -> "HydrationContext":
|
27
|
-
variables
|
28
|
-
|
29
|
-
|
31
|
+
from prefect.server.models.variables import read_variables
|
32
|
+
|
33
|
+
if render_workspace_variables:
|
34
|
+
variables = await read_variables(
|
35
|
+
session=session,
|
36
|
+
)
|
37
|
+
else:
|
38
|
+
variables = []
|
39
|
+
|
30
40
|
return cls(
|
31
41
|
workspace_variables={
|
32
42
|
variable.name: variable.value for variable in variables
|
33
43
|
},
|
34
44
|
raise_on_error=raise_on_error,
|
45
|
+
render_jinja=render_jinja,
|
46
|
+
render_workspace_variables=render_workspace_variables,
|
35
47
|
)
|
36
48
|
|
37
49
|
|
38
|
-
Handler: TypeAlias = Callable[[
|
50
|
+
Handler: TypeAlias = Callable[[dict, HydrationContext], Any]
|
39
51
|
PrefectKind: TypeAlias = Optional[str]
|
40
52
|
|
41
53
|
_handlers: Dict[PrefectKind, Handler] = {}
|
@@ -93,6 +105,12 @@ class ValueNotFound(KeyNotFound):
|
|
93
105
|
return "value"
|
94
106
|
|
95
107
|
|
108
|
+
class TemplateNotFound(KeyNotFound):
|
109
|
+
@property
|
110
|
+
def key(self):
|
111
|
+
return "template"
|
112
|
+
|
113
|
+
|
96
114
|
class VariableNameNotFound(KeyNotFound):
|
97
115
|
@property
|
98
116
|
def key(self):
|
@@ -108,6 +126,15 @@ class InvalidJSON(HydrationError):
|
|
108
126
|
return message
|
109
127
|
|
110
128
|
|
129
|
+
class InvalidJinja(HydrationError):
|
130
|
+
@property
|
131
|
+
def message(self):
|
132
|
+
message = "Invalid jinja"
|
133
|
+
if self.detail:
|
134
|
+
message += f": {self.detail}"
|
135
|
+
return message
|
136
|
+
|
137
|
+
|
111
138
|
class WorkspaceVariableNotFound(HydrationError):
|
112
139
|
@property
|
113
140
|
def variable_name(self) -> str:
|
@@ -116,7 +143,25 @@ class WorkspaceVariableNotFound(HydrationError):
|
|
116
143
|
|
117
144
|
@property
|
118
145
|
def message(self):
|
119
|
-
return f"Variable '{self.detail}' not found."
|
146
|
+
return f"Variable '{self.detail}' not found in workspace."
|
147
|
+
|
148
|
+
|
149
|
+
class WorkspaceVariable(Placeholder):
|
150
|
+
def __init__(self, variable_name: str):
|
151
|
+
self.variable_name = variable_name
|
152
|
+
|
153
|
+
def __eq__(self, other):
|
154
|
+
return (
|
155
|
+
isinstance(other, type(self)) and self.variable_name == other.variable_name
|
156
|
+
)
|
157
|
+
|
158
|
+
|
159
|
+
class ValidJinja(Placeholder):
|
160
|
+
def __init__(self, template: str):
|
161
|
+
self.template = template
|
162
|
+
|
163
|
+
def __eq__(self, other):
|
164
|
+
return isinstance(other, type(self)) and self.template == other.template
|
120
165
|
|
121
166
|
|
122
167
|
def handler(kind: PrefectKind) -> Callable:
|
@@ -127,7 +172,7 @@ def handler(kind: PrefectKind) -> Callable:
|
|
127
172
|
return decorator
|
128
173
|
|
129
174
|
|
130
|
-
def call_handler(kind: PrefectKind, obj:
|
175
|
+
def call_handler(kind: PrefectKind, obj: dict, ctx: HydrationContext) -> Any:
|
131
176
|
if kind not in _handlers:
|
132
177
|
return (obj or {}).get("value", None)
|
133
178
|
|
@@ -138,7 +183,7 @@ def call_handler(kind: PrefectKind, obj: Dict, ctx: HydrationContext) -> Any:
|
|
138
183
|
|
139
184
|
|
140
185
|
@handler("none")
|
141
|
-
def null_handler(obj:
|
186
|
+
def null_handler(obj: dict, ctx: HydrationContext):
|
142
187
|
if "value" in obj:
|
143
188
|
# null handler is a pass through, so we want to continue to hydrate
|
144
189
|
return _hydrate(obj["value"], ctx)
|
@@ -147,7 +192,7 @@ def null_handler(obj: Dict, ctx: HydrationContext):
|
|
147
192
|
|
148
193
|
|
149
194
|
@handler("json")
|
150
|
-
def json_handler(obj:
|
195
|
+
def json_handler(obj: dict, ctx: HydrationContext):
|
151
196
|
if "value" in obj:
|
152
197
|
if isinstance(obj["value"], dict):
|
153
198
|
dehydrated_json = _hydrate(obj["value"], ctx)
|
@@ -167,14 +212,38 @@ def json_handler(obj: Dict, ctx: HydrationContext):
|
|
167
212
|
return RemoveValue()
|
168
213
|
|
169
214
|
|
215
|
+
@handler("jinja")
|
216
|
+
def jinja_handler(obj: dict, ctx: HydrationContext):
|
217
|
+
if "template" in obj:
|
218
|
+
if isinstance(obj["template"], dict):
|
219
|
+
dehydrated_jinja = _hydrate(obj["template"], ctx)
|
220
|
+
else:
|
221
|
+
dehydrated_jinja = obj["template"]
|
222
|
+
|
223
|
+
try:
|
224
|
+
validate_user_template(dehydrated_jinja)
|
225
|
+
except (jinja2.exceptions.TemplateSyntaxError, TemplateSecurityError) as exc:
|
226
|
+
return InvalidJinja(detail=str(exc))
|
227
|
+
|
228
|
+
if ctx.render_jinja:
|
229
|
+
return render_user_template_sync(dehydrated_jinja, ctx.jinja_context)
|
230
|
+
else:
|
231
|
+
return ValidJinja(template=dehydrated_jinja)
|
232
|
+
else:
|
233
|
+
return TemplateNotFound()
|
234
|
+
|
235
|
+
|
170
236
|
@handler("workspace_variable")
|
171
|
-
def workspace_variable_handler(obj:
|
237
|
+
def workspace_variable_handler(obj: dict, ctx: HydrationContext):
|
172
238
|
if "variable_name" in obj:
|
173
239
|
if isinstance(obj["variable_name"], dict):
|
174
240
|
dehydrated_variable = _hydrate(obj["variable_name"], ctx)
|
175
241
|
else:
|
176
242
|
dehydrated_variable = obj["variable_name"]
|
177
243
|
|
244
|
+
if not ctx.render_workspace_variables:
|
245
|
+
return WorkspaceVariable(variable_name=obj["variable_name"])
|
246
|
+
|
178
247
|
if dehydrated_variable in ctx.workspace_variables:
|
179
248
|
return ctx.workspace_variables[dehydrated_variable]
|
180
249
|
else:
|
@@ -191,7 +260,7 @@ def workspace_variable_handler(obj: Dict, ctx: HydrationContext):
|
|
191
260
|
return RemoveValue()
|
192
261
|
|
193
262
|
|
194
|
-
def hydrate(obj:
|
263
|
+
def hydrate(obj: dict, ctx: Optional[HydrationContext] = None):
|
195
264
|
res = _hydrate(obj, ctx)
|
196
265
|
|
197
266
|
if _remove_value(res):
|
@@ -200,7 +269,7 @@ def hydrate(obj: Dict, ctx: Optional[HydrationContext] = None):
|
|
200
269
|
return res
|
201
270
|
|
202
271
|
|
203
|
-
def _hydrate(obj, ctx: Optional[HydrationContext] = None):
|
272
|
+
def _hydrate(obj, ctx: Optional[HydrationContext] = None) -> Any:
|
204
273
|
if ctx is None:
|
205
274
|
ctx = HydrationContext()
|
206
275
|
|
@@ -232,7 +232,7 @@ def preprocess_schema(schema):
|
|
232
232
|
process_properties(schema["properties"], required_fields)
|
233
233
|
|
234
234
|
if "definitions" in schema: # Also process definitions for reused models
|
235
|
-
for definition in schema["definitions"].values():
|
235
|
+
for definition in (schema["definitions"] or {}).values():
|
236
236
|
if "properties" in definition:
|
237
237
|
required_fields = definition.get("required", [])
|
238
238
|
process_properties(definition["properties"], required_fields)
|
prefect/variables.py
CHANGED
@@ -30,11 +30,11 @@ class Variable(VariableRequest):
|
|
30
30
|
"""
|
31
31
|
Sets a new variable. If one exists with the same name, user must pass `overwrite=True`
|
32
32
|
```
|
33
|
-
from prefect import
|
33
|
+
from prefect.variables import Variable
|
34
34
|
|
35
35
|
@flow
|
36
36
|
def my_flow():
|
37
|
-
var =
|
37
|
+
var = Variable.set(name="my_var",value="test_value", tags=["hi", "there"], overwrite=True)
|
38
38
|
```
|
39
39
|
or
|
40
40
|
```
|
@@ -69,11 +69,11 @@ class Variable(VariableRequest):
|
|
69
69
|
"""
|
70
70
|
Get a variable by name. If doesn't exist return the default.
|
71
71
|
```
|
72
|
-
from prefect import
|
72
|
+
from prefect.variables import Variable
|
73
73
|
|
74
74
|
@flow
|
75
75
|
def my_flow():
|
76
|
-
var =
|
76
|
+
var = Variable.get("my_var")
|
77
77
|
```
|
78
78
|
or
|
79
79
|
```
|