diffusers-workflow 0.4.0__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.
- diffusers_workflow-0.4.0.dist-info/METADATA +318 -0
- diffusers_workflow-0.4.0.dist-info/RECORD +260 -0
- diffusers_workflow-0.4.0.dist-info/WHEEL +5 -0
- diffusers_workflow-0.4.0.dist-info/entry_points.txt +7 -0
- diffusers_workflow-0.4.0.dist-info/licenses/LICENSE +201 -0
- diffusers_workflow-0.4.0.dist-info/top_level.txt +2 -0
- dw/__init__.py +440 -0
- dw/adapter_compatibility.py +226 -0
- dw/arguments.py +1231 -0
- dw/assessment_rules.py +159 -0
- dw/assets.py +130 -0
- dw/cache_blocks.json +16 -0
- dw/cache_blocks.py +146 -0
- dw/community_pipelines/pipeline_flux_rf_inversion.py +1184 -0
- dw/content_types.py +150 -0
- dw/dissolve_frame_errors.py +121 -0
- dw/docs/ACCELERATION.md +352 -0
- dw/docs/AGENT_LOOP.md +95 -0
- dw/docs/DEPENDENCIES.md +91 -0
- dw/docs/IP_ADAPTER.md +109 -0
- dw/docs/LORAS.md +131 -0
- dw/docs/MCP.md +517 -0
- dw/docs/PROMPT_WEIGHTING.md +78 -0
- dw/docs/QUANTIZATION.md +230 -0
- dw/docs/RECIPES_24GB.md +201 -0
- dw/docs/RELEASING.md +195 -0
- dw/docs/REMOTE.md +140 -0
- dw/docs/REPL_COMMANDS.md +121 -0
- dw/docs/REPL_WORKER_GUIDE.md +51 -0
- dw/docs/SECURITY.md +272 -0
- dw/docs/SECURITY_QUICKREF.md +112 -0
- dw/docs/SERVER.md +679 -0
- dw/docs/TASKS.md +1741 -0
- dw/docs/TESTING.md +71 -0
- dw/docs/WORKFLOW_GUIDE.md +2038 -0
- dw/docs/WORKSPACES.md +316 -0
- dw/download_watch.py +335 -0
- dw/elision.py +306 -0
- dw/events.py +275 -0
- dw/for_each.py +409 -0
- dw/host_memory.py +258 -0
- dw/host_memory_projection.py +230 -0
- dw/hub_cache.py +432 -0
- dw/introspection.py +1228 -0
- dw/kernel_availability.py +208 -0
- dw/locations.py +599 -0
- dw/log_setup.py +45 -0
- dw/loudness.py +82 -0
- dw/media_audio.py +217 -0
- dw/media_frames.py +367 -0
- dw/media_info.py +297 -0
- dw/pipeline_processors/chain.py +821 -0
- dw/pipeline_processors/config_objects.py +237 -0
- dw/pipeline_processors/pipeline.py +2297 -0
- dw/pipeline_processors/remote.py +46 -0
- dw/plan.py +920 -0
- dw/previous_results.py +411 -0
- dw/probe_paths.py +59 -0
- dw/prompt_schema.json +48 -0
- dw/prompt_weighting.py +378 -0
- dw/prompts.py +159 -0
- dw/realize.py +250 -0
- dw/reference_limits.py +215 -0
- dw/reference_names.py +125 -0
- dw/repl.py +338 -0
- dw/repl_commands.py +836 -0
- dw/repl_worker.py +159 -0
- dw/result.py +1720 -0
- dw/result_fps.py +82 -0
- dw/run.py +162 -0
- dw/runs.py +768 -0
- dw/scalar_result_validation.py +97 -0
- dw/schema.py +283 -0
- dw/security.py +1038 -0
- dw/select_validation.py +115 -0
- dw/serve.py +277 -0
- dw/server/__init__.py +2 -0
- dw/server/app.py +4586 -0
- dw/server/assess.py +132 -0
- dw/server/catalog_shape.py +487 -0
- dw/server/enhancers.py +129 -0
- dw/server/exports.py +480 -0
- dw/server/guides.py +257 -0
- dw/server/jobs.py +1561 -0
- dw/server/mcp_mount.py +95 -0
- dw/server/netinfo.py +124 -0
- dw/server/observed_cost.py +379 -0
- dw/server/sysinfo.py +71 -0
- dw/server/ui/assets/abap-08VXUWAP.js +1 -0
- dw/server/ui/assets/apex-BWPQTe0t.js +1 -0
- dw/server/ui/assets/azcli-Bc_sGQ0U.js +1 -0
- dw/server/ui/assets/bat-i0X4ZdIN.js +1 -0
- dw/server/ui/assets/bicep-B5-_aFwp.js +2 -0
- dw/server/ui/assets/cameligo-DMUM7wLl.js +1 -0
- dw/server/ui/assets/clojure-Cm7r79vr.js +1 -0
- dw/server/ui/assets/codicon-Brq4_Ui5.ttf +0 -0
- dw/server/ui/assets/coffee-Ba7i2nA0.js +1 -0
- dw/server/ui/assets/cpp-C7h46wYY.js +1 -0
- dw/server/ui/assets/csharp-BKxtCVv1.js +1 -0
- dw/server/ui/assets/csp-bTuwJoIa.js +1 -0
- dw/server/ui/assets/css-DIMkf-bt.js +3 -0
- dw/server/ui/assets/css.worker-B3ciXF_0.js +93 -0
- dw/server/ui/assets/cssMode-CPznxfY8.js +1 -0
- dw/server/ui/assets/cypher-CVaqCwHa.js +1 -0
- dw/server/ui/assets/dart-onAF5SnQ.js +1 -0
- dw/server/ui/assets/dockerfile-DZFCIeNp.js +1 -0
- dw/server/ui/assets/ecl-D05T4iGw.js +1 -0
- dw/server/ui/assets/editor-jjEx9u7D.css +1 -0
- dw/server/ui/assets/editor.api-CpWcotrd.js +847 -0
- dw/server/ui/assets/editor.worker-q-txB4vs.js +30 -0
- dw/server/ui/assets/elixir-6RTg0lbw.js +1 -0
- dw/server/ui/assets/flow9-C5_-GSwl.js +1 -0
- dw/server/ui/assets/freemarker2-CXtRM8N4.js +3 -0
- dw/server/ui/assets/fsharp-C8Ef5oNN.js +1 -0
- dw/server/ui/assets/go-C-y9NEjX.js +1 -0
- dw/server/ui/assets/graphql-fmXr3nnJ.js +1 -0
- dw/server/ui/assets/handlebars-N7x-6NMY.js +1 -0
- dw/server/ui/assets/hcl-CpzslTdj.js +1 -0
- dw/server/ui/assets/html-PhsdjHSr.js +1 -0
- dw/server/ui/assets/html.worker-C93Ht9o9.js +506 -0
- dw/server/ui/assets/htmlMode-Dgj0SEok.js +1 -0
- dw/server/ui/assets/index-3Vw6WAPW.css +1 -0
- dw/server/ui/assets/index-DgrYhQd9.js +43 -0
- dw/server/ui/assets/ini-sBoK_t0W.js +1 -0
- dw/server/ui/assets/java-BEtHBSE6.js +1 -0
- dw/server/ui/assets/javascript-BJqN9Qhv.js +1 -0
- dw/server/ui/assets/json.worker-B2V3pomh.js +62 -0
- dw/server/ui/assets/jsonMode-DbM4SWSv.js +7 -0
- dw/server/ui/assets/julia-Bri6UV-V.js +1 -0
- dw/server/ui/assets/kotlin-BOotOW0E.js +1 -0
- dw/server/ui/assets/less-B9JPFI3C.js +2 -0
- dw/server/ui/assets/lexon-CfSJPG6W.js +1 -0
- dw/server/ui/assets/liquid-BWr8lEc4.js +1 -0
- dw/server/ui/assets/lspLanguageFeatures-C1iGuDyZ.js +4 -0
- dw/server/ui/assets/lua-CsQS60Ue.js +1 -0
- dw/server/ui/assets/m3-D-oSqn_W.js +1 -0
- dw/server/ui/assets/markdown-Cimd5fb3.js +1 -0
- dw/server/ui/assets/mdx-DAdMi_0p.js +1 -0
- dw/server/ui/assets/mips-CIPQ_RoX.js +1 -0
- dw/server/ui/assets/monaco--ixms01u.css +1 -0
- dw/server/ui/assets/monaco-BGCeEqaw.js +56 -0
- dw/server/ui/assets/msdax-DauUninz.js +1 -0
- dw/server/ui/assets/mysql-SOo6toE5.js +1 -0
- dw/server/ui/assets/objective-c-FvmIjYaQ.js +1 -0
- dw/server/ui/assets/pascal-DrH0SRf2.js +1 -0
- dw/server/ui/assets/pascaligo-D-ptJ9y-.js +1 -0
- dw/server/ui/assets/perl-oz_6vUea.js +1 -0
- dw/server/ui/assets/pgsql-DTj74zXo.js +1 -0
- dw/server/ui/assets/php-nr791fC2.js +1 -0
- dw/server/ui/assets/pla-CopQ2nXW.js +1 -0
- dw/server/ui/assets/postiats-43DmfD33.js +1 -0
- dw/server/ui/assets/powerquery-D3hlyOfw.js +1 -0
- dw/server/ui/assets/powershell-DmHpPYUd.js +1 -0
- dw/server/ui/assets/protobuf-C531GsRP.js +2 -0
- dw/server/ui/assets/pug-Z5eAx3Zn.js +1 -0
- dw/server/ui/assets/python-Bcn70HdC.js +1 -0
- dw/server/ui/assets/qsharp-DkqhCAOL.js +1 -0
- dw/server/ui/assets/r-BwWrilGY.js +1 -0
- dw/server/ui/assets/razor-D1HmNnby.js +1 -0
- dw/server/ui/assets/redis-ClamHrr6.js +1 -0
- dw/server/ui/assets/redshift-DT7zqm-g.js +1 -0
- dw/server/ui/assets/restructuredtext-BYgofb2h.js +1 -0
- dw/server/ui/assets/ruby-DezsRK8O.js +1 -0
- dw/server/ui/assets/rust-DdL9SqIa.js +1 -0
- dw/server/ui/assets/sb-CcwsVR0C.js +1 -0
- dw/server/ui/assets/scala-DHpiXF5c.js +1 -0
- dw/server/ui/assets/scheme-BeGwcela.js +1 -0
- dw/server/ui/assets/scss-gp-XZpBa.js +3 -0
- dw/server/ui/assets/shell-CC2rA5mh.js +1 -0
- dw/server/ui/assets/solidity-BEEn4gHE.js +1 -0
- dw/server/ui/assets/sophia-CRfGWb83.js +1 -0
- dw/server/ui/assets/sparql-D_Lu-MrJ.js +1 -0
- dw/server/ui/assets/sql-NEE52Syq.js +1 -0
- dw/server/ui/assets/st-DbInun42.js +1 -0
- dw/server/ui/assets/swift-Bxkupp3x.js +1 -0
- dw/server/ui/assets/systemverilog-Bz4Y3fRF.js +1 -0
- dw/server/ui/assets/tcl-DISqw1ZD.js +1 -0
- dw/server/ui/assets/ts.worker-D7T1-Ig5.js +67738 -0
- dw/server/ui/assets/tsMode-D6u0XmOW.js +11 -0
- dw/server/ui/assets/twig-De2hgUGE.js +1 -0
- dw/server/ui/assets/typescript-BU6v-LMV.js +1 -0
- dw/server/ui/assets/typespec-B8J7ngcE.js +1 -0
- dw/server/ui/assets/vb-DV3o63ZY.js +1 -0
- dw/server/ui/assets/wgsl-DpFanUEy.js +298 -0
- dw/server/ui/assets/workers-Cn7cTUKr.js +1 -0
- dw/server/ui/assets/xml--0LP2Lwk.js +1 -0
- dw/server/ui/assets/yaml-mpBg9jnt.js +1 -0
- dw/server/ui/index.html +17 -0
- dw/server/updater.py +192 -0
- dw/settings.py +98 -0
- dw/shot_span_preflight.py +116 -0
- dw/shots.py +359 -0
- dw/slice_preflight.py +148 -0
- dw/step.py +187 -0
- dw/step_cache.py +442 -0
- dw/subfolders.py +107 -0
- dw/task_domains.py +307 -0
- dw/tasks/assess.py +826 -0
- dw/tasks/audio_transcription.py +88 -0
- dw/tasks/audio_utils.py +1862 -0
- dw/tasks/background_remover.py +43 -0
- dw/tasks/borders.py +113 -0
- dw/tasks/compose_text.py +74 -0
- dw/tasks/concat_videos.py +300 -0
- dw/tasks/depth_estimator.py +54 -0
- dw/tasks/diffusion_upscale.py +109 -0
- dw/tasks/dissolve_videos.py +342 -0
- dw/tasks/format_messages.py +24 -0
- dw/tasks/gather.py +173 -0
- dw/tasks/grade.py +97 -0
- dw/tasks/image_to_text.py +43 -0
- dw/tasks/image_utils.py +764 -0
- dw/tasks/interpolate_frames.py +252 -0
- dw/tasks/judge.py +68 -0
- dw/tasks/model_cache.py +55 -0
- dw/tasks/pair_audio.py +268 -0
- dw/tasks/qr_code.py +19 -0
- dw/tasks/restore_faces.py +175 -0
- dw/tasks/rife_model.py +192 -0
- dw/tasks/segment.py +121 -0
- dw/tasks/select.py +111 -0
- dw/tasks/speech_generation.py +228 -0
- dw/tasks/stabilize.py +129 -0
- dw/tasks/task.py +920 -0
- dw/tasks/tensor_image.py +57 -0
- dw/tasks/text_generation.py +169 -0
- dw/tasks/text_sections.py +80 -0
- dw/tasks/upscale.py +203 -0
- dw/tasks/video_utils.py +624 -0
- dw/tasks/zoe_depth.py +71 -0
- dw/teacache.py +381 -0
- dw/teacache_models.json +99 -0
- dw/test.py +29 -0
- dw/type_helpers.py +231 -0
- dw/validate.py +68 -0
- dw/variable_constraints.py +444 -0
- dw/variables.py +443 -0
- dw/video_extensions.py +141 -0
- dw/vram_estimate.py +116 -0
- dw/worker.py +764 -0
- dw/workflow.py +2007 -0
- dw/workflow_schema.json +1346 -0
- dw/workflow_sources.py +383 -0
- dw/workflows/h3_context_ir.json +57 -0
- dw/workflows/test.json +31 -0
- dw/workspace.py +730 -0
- dw_mcp/__init__.py +6 -0
- dw_mcp/__main__.py +133 -0
- dw_mcp/assets.py +336 -0
- dw_mcp/authoring.py +114 -0
- dw_mcp/catalog.py +360 -0
- dw_mcp/client.py +486 -0
- dw_mcp/diagnose.py +371 -0
- dw_mcp/exports.py +84 -0
- dw_mcp/guides.py +35 -0
- dw_mcp/media.py +638 -0
- dw_mcp/models.py +97 -0
- dw_mcp/prompts.py +104 -0
- dw_mcp/server.py +1343 -0
- dw_mcp/workspaces.py +212 -0
dw/type_helpers.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import inspect
|
|
3
|
+
import types
|
|
4
|
+
|
|
5
|
+
from .security import (
|
|
6
|
+
TRUSTED_TOP_LEVEL_PACKAGES,
|
|
7
|
+
UntrustedWorkflowError,
|
|
8
|
+
require_constructible_class,
|
|
9
|
+
require_trusted_dotted_name,
|
|
10
|
+
workflows_are_trusted,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_type(module_name, type_name):
|
|
15
|
+
module = __import__(module_name)
|
|
16
|
+
return getattr(module, type_name)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _accepts_dtype(key):
|
|
20
|
+
return key is not None and (key == "dtype" or key.endswith("_dtype"))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _defining_package(value):
|
|
24
|
+
"""The top-level package a class or function was defined in, or None."""
|
|
25
|
+
module = getattr(value, "__module__", None)
|
|
26
|
+
if not isinstance(module, str) or not module:
|
|
27
|
+
return None
|
|
28
|
+
return module.split(".", 1)[0]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _require_defined_inside(name, value, what):
|
|
32
|
+
"""Refuse a class an untrusted name reached through a re-export.
|
|
33
|
+
|
|
34
|
+
The allowlist is checked on the name's top-level package, but a module
|
|
35
|
+
re-exports what it imported: a name under an allowed package can resolve
|
|
36
|
+
to a class defined anywhere. So the class itself must have been defined
|
|
37
|
+
in an allowed package, not just be reachable from one.
|
|
38
|
+
"""
|
|
39
|
+
package = _defining_package(value)
|
|
40
|
+
if package in TRUSTED_TOP_LEVEL_PACKAGES:
|
|
41
|
+
return
|
|
42
|
+
raise UntrustedWorkflowError(
|
|
43
|
+
f"Refusing to load {what} '{name}': it resolves to "
|
|
44
|
+
f"'{getattr(value, '__module__', None)}."
|
|
45
|
+
f"{getattr(value, '__qualname__', type(value).__name__)}', defined "
|
|
46
|
+
f"outside the ecosystem ({', '.join(TRUSTED_TOP_LEVEL_PACKAGES)}) this "
|
|
47
|
+
f"workflow is allowed to reach untrusted. Pass --trust-workflows if "
|
|
48
|
+
f"you trust this workflow's source."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def require_loadable_type(name, value, key=None, constructed=True):
|
|
53
|
+
"""Refuse a type reference that resolved to something other than a class,
|
|
54
|
+
unless the workflow is trusted.
|
|
55
|
+
|
|
56
|
+
A '*_type' value is constructed with the workflow's own arguments, so an
|
|
57
|
+
allowlisted package is only safe if what the name reaches is a class:
|
|
58
|
+
'torch.hub.load' is in 'torch' and runs a GitHub repo's code when called.
|
|
59
|
+
A 'dtype' or '*_dtype' key names a torch.dtype, which is data, not a
|
|
60
|
+
class, and is accepted there.
|
|
61
|
+
|
|
62
|
+
A class must also be defined inside TRUSTED_TOP_LEVEL_PACKAGES, not
|
|
63
|
+
merely re-exported by a module there, and be one an untrusted workflow
|
|
64
|
+
may construct (security.is_constructible_class) - a class inside an
|
|
65
|
+
allowed package can still do anything in its constructor.
|
|
66
|
+
`constructed=False` skips only that last check, for a caller resolving a
|
|
67
|
+
server-owned name it never constructs (cache_blocks' registry).
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
UntrustedWorkflowError: If untrusted and `value` is neither a class
|
|
71
|
+
nor, under a dtype key, a torch.dtype, or is a class defined
|
|
72
|
+
outside the allowed packages or not constructible untrusted
|
|
73
|
+
"""
|
|
74
|
+
if workflows_are_trusted():
|
|
75
|
+
return value
|
|
76
|
+
if inspect.isclass(value):
|
|
77
|
+
_require_defined_inside(name, value, key or "a type reference")
|
|
78
|
+
if constructed:
|
|
79
|
+
require_constructible_class(name, value, key or "a type reference")
|
|
80
|
+
return value
|
|
81
|
+
if _accepts_dtype(key):
|
|
82
|
+
import torch
|
|
83
|
+
|
|
84
|
+
if isinstance(value, torch.dtype):
|
|
85
|
+
return value
|
|
86
|
+
|
|
87
|
+
kind = "module" if isinstance(value, types.ModuleType) else type(value).__name__
|
|
88
|
+
allowed = "a class or a torch.dtype" if _accepts_dtype(key) else "a class"
|
|
89
|
+
raise UntrustedWorkflowError(
|
|
90
|
+
f"Refusing to load {key or 'a type reference'} '{name}': it is a {kind}, "
|
|
91
|
+
f"not a class. An untrusted workflow's type reference must name "
|
|
92
|
+
f"{allowed} - anything else could be called with the workflow's "
|
|
93
|
+
f"arguments. Pass --trust-workflows if you trust this workflow's source."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def load_type_from_name(type_name, key=None, constructed=True):
|
|
98
|
+
if "." in type_name:
|
|
99
|
+
return load_type_from_full_name(type_name, key, constructed)
|
|
100
|
+
|
|
101
|
+
return require_loadable_type(
|
|
102
|
+
type_name, get_type("diffusers", type_name), key, constructed
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def load_type_from_full_name(full_name, key=None, constructed=True):
|
|
107
|
+
# A bare name resolves against diffusers regardless of trust; a dotted
|
|
108
|
+
# name imports whatever module it names, which is the code-execution
|
|
109
|
+
# surface an untrusted workflow is refused unless it stays in-ecosystem
|
|
110
|
+
require_trusted_dotted_name(full_name, "a dotted type reference")
|
|
111
|
+
|
|
112
|
+
# Split the full name into module path and object name
|
|
113
|
+
module_path, object_name = full_name.rsplit(".", 1)
|
|
114
|
+
|
|
115
|
+
# Dynamically import the module
|
|
116
|
+
module = importlib.import_module(module_path)
|
|
117
|
+
|
|
118
|
+
# Get the object from the module
|
|
119
|
+
return require_loadable_type(
|
|
120
|
+
full_name, getattr(module, object_name), key, constructed
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def has_method(o, name):
|
|
125
|
+
return callable(getattr(o, name, None))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _require_walk_stays_inside(name, parts, value, index):
|
|
129
|
+
"""Refuse a constant walk that leaves the allowed packages.
|
|
130
|
+
|
|
131
|
+
The allowlist is checked on the name's top-level package, but a module
|
|
132
|
+
re-exports what it imported: 'torch.os.environ' starts in torch and ends
|
|
133
|
+
in the server's environment. So every module the walk passes through must
|
|
134
|
+
itself be in an allowed package, and no segment may be private. The same
|
|
135
|
+
holds for what is not a module: a class or function must be defined in an
|
|
136
|
+
allowed package, and any other value must be an instance of a builtin
|
|
137
|
+
type or of one defined in an allowed package - a trusted module that ran
|
|
138
|
+
'from os import environ' would otherwise hand over the environment.
|
|
139
|
+
"""
|
|
140
|
+
if parts[index].startswith("_"):
|
|
141
|
+
raise UntrustedWorkflowError(
|
|
142
|
+
f"Refusing the constant '{name}': '{parts[index]}' is a private "
|
|
143
|
+
f"name, and an untrusted workflow may only read public ones. "
|
|
144
|
+
f"Pass --trust-workflows if you trust this workflow's source."
|
|
145
|
+
)
|
|
146
|
+
if isinstance(value, types.ModuleType):
|
|
147
|
+
top_level = value.__name__.split(".", 1)[0]
|
|
148
|
+
if top_level not in TRUSTED_TOP_LEVEL_PACKAGES:
|
|
149
|
+
raise UntrustedWorkflowError(
|
|
150
|
+
f"Refusing the constant '{name}': "
|
|
151
|
+
f"'{'.'.join(parts[: index + 1])}' is the '{value.__name__}' "
|
|
152
|
+
f"module, which is outside the ecosystem "
|
|
153
|
+
f"({', '.join(TRUSTED_TOP_LEVEL_PACKAGES)}) this workflow is "
|
|
154
|
+
f"allowed to reach untrusted. Pass --trust-workflows if you "
|
|
155
|
+
f"trust this workflow's source."
|
|
156
|
+
)
|
|
157
|
+
return
|
|
158
|
+
if value is None:
|
|
159
|
+
return
|
|
160
|
+
if inspect.isclass(value) or inspect.isroutine(value):
|
|
161
|
+
_require_defined_inside(name, value, "the constant")
|
|
162
|
+
return
|
|
163
|
+
package = type(value).__module__.split(".", 1)[0]
|
|
164
|
+
if package != "builtins" and package not in TRUSTED_TOP_LEVEL_PACKAGES:
|
|
165
|
+
raise UntrustedWorkflowError(
|
|
166
|
+
f"Refusing the constant '{name}': "
|
|
167
|
+
f"'{'.'.join(parts[: index + 1])}' is a "
|
|
168
|
+
f"'{type(value).__module__}.{type(value).__qualname__}', a type "
|
|
169
|
+
f"defined outside the ecosystem "
|
|
170
|
+
f"({', '.join(TRUSTED_TOP_LEVEL_PACKAGES)}) this workflow is "
|
|
171
|
+
f"allowed to reach untrusted. Pass --trust-workflows if you "
|
|
172
|
+
f"trust this workflow's source."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def load_constant_from_name(name):
|
|
177
|
+
"""Load a constant declared in python, by its dotted name.
|
|
178
|
+
|
|
179
|
+
The leading run of names that imports is the module the constant lives in and
|
|
180
|
+
the rest are read from it, so a constant held in a dataclass is reachable
|
|
181
|
+
('...utils.GEMMA4_PROMPT_ENHANCEMENT_CONFIG.max_new_tokens') as well as one
|
|
182
|
+
declared at module scope. A bare name is read from diffusers, matching the way
|
|
183
|
+
a bare type reference resolves.
|
|
184
|
+
|
|
185
|
+
Untrusted, the walk may not pass through a private name or a module outside
|
|
186
|
+
TRUSTED_TOP_LEVEL_PACKAGES.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
name: Dotted name of the constant
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
The value the name refers to
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
ImportError: If no leading part of the name names a module
|
|
196
|
+
AttributeError: If the module has no such attribute
|
|
197
|
+
UntrustedWorkflowError: If untrusted and the walk leaves the allowed
|
|
198
|
+
packages or reads a private name
|
|
199
|
+
"""
|
|
200
|
+
# A dotted constant imports the module it names before anything reads
|
|
201
|
+
# the attribute - the same code-execution surface as a dotted type
|
|
202
|
+
parts = name.split(".")
|
|
203
|
+
guarded = not workflows_are_trusted()
|
|
204
|
+
if "." in name:
|
|
205
|
+
require_trusted_dotted_name(name, "a constant: reference")
|
|
206
|
+
if guarded:
|
|
207
|
+
# Checked before anything imports: a private module's import runs
|
|
208
|
+
# its code whatever the walk would have read from it
|
|
209
|
+
for index, part in enumerate(parts):
|
|
210
|
+
if part.startswith("_"):
|
|
211
|
+
_require_walk_stays_inside(name, parts, None, index)
|
|
212
|
+
|
|
213
|
+
module, attributes, start = None, parts, 0
|
|
214
|
+
for i in range(len(parts) - 1, 0, -1):
|
|
215
|
+
try:
|
|
216
|
+
module = importlib.import_module(".".join(parts[:i]))
|
|
217
|
+
attributes, start = parts[i:], i
|
|
218
|
+
break
|
|
219
|
+
except ImportError:
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
if module is None:
|
|
223
|
+
# No dotted module path - a bare name, read from diffusers
|
|
224
|
+
module = importlib.import_module("diffusers")
|
|
225
|
+
|
|
226
|
+
value = module
|
|
227
|
+
for offset, attribute in enumerate(attributes):
|
|
228
|
+
value = getattr(value, attribute)
|
|
229
|
+
if guarded:
|
|
230
|
+
_require_walk_stays_inside(name, parts, value, start + offset)
|
|
231
|
+
return value
|
dw/validate.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
from .workflow import workflow_from_file
|
|
4
|
+
from . import startup
|
|
5
|
+
from .security import validate_workflow_path, set_trust_workflows, SecurityError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
parser = argparse.ArgumentParser(description="Validate a workflow from a file.")
|
|
10
|
+
parser.add_argument(
|
|
11
|
+
"file_name", type=str, help="The filespec of the workflow to validate"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
"-l",
|
|
16
|
+
"--log_level",
|
|
17
|
+
type=str,
|
|
18
|
+
default="INFO",
|
|
19
|
+
help="Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
|
|
20
|
+
)
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
"--trust-workflows",
|
|
23
|
+
action="store_true",
|
|
24
|
+
default=False,
|
|
25
|
+
help="Trust this workflow file to execute arbitrary Python: allow "
|
|
26
|
+
"pre_load_modules and any dotted *_type/*_dtype/dtype/config_type "
|
|
27
|
+
"value, not just ones inside the diffusers/torch/transformers/"
|
|
28
|
+
"quantization-backend ecosystem the tool already depends on. Off "
|
|
29
|
+
"by default - see docs/SECURITY.md's Trust model. Only pass this "
|
|
30
|
+
"for a workflow file whose source you trust.",
|
|
31
|
+
)
|
|
32
|
+
args = parser.parse_args()
|
|
33
|
+
|
|
34
|
+
set_trust_workflows(args.trust_workflows)
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
validated_file_path = validate_workflow_path(args.file_name)
|
|
38
|
+
if not os.path.exists(validated_file_path):
|
|
39
|
+
raise FileNotFoundError(f"File {validated_file_path} does not exist")
|
|
40
|
+
except SecurityError as e:
|
|
41
|
+
print(f"Error: Security validation failed: {e}")
|
|
42
|
+
exit(1)
|
|
43
|
+
|
|
44
|
+
startup(args.log_level)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
workflow = workflow_from_file(validated_file_path, ".")
|
|
48
|
+
except Exception as e:
|
|
49
|
+
print(f"Error validating workflow '{args.file_name}': {e}")
|
|
50
|
+
exit(1)
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
# Workflow.validate() names the JSON path of a schema failure and
|
|
55
|
+
# carries the 'Validation error' prefix exactly once
|
|
56
|
+
workflow.validate()
|
|
57
|
+
print("Workflow validated successfully")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(
|
|
60
|
+
str(e)
|
|
61
|
+
if str(e).startswith("Validation error")
|
|
62
|
+
else f"Error validating workflow '{args.file_name}': {e}"
|
|
63
|
+
)
|
|
64
|
+
exit(1)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
main()
|