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.
Files changed (260) hide show
  1. diffusers_workflow-0.4.0.dist-info/METADATA +318 -0
  2. diffusers_workflow-0.4.0.dist-info/RECORD +260 -0
  3. diffusers_workflow-0.4.0.dist-info/WHEEL +5 -0
  4. diffusers_workflow-0.4.0.dist-info/entry_points.txt +7 -0
  5. diffusers_workflow-0.4.0.dist-info/licenses/LICENSE +201 -0
  6. diffusers_workflow-0.4.0.dist-info/top_level.txt +2 -0
  7. dw/__init__.py +440 -0
  8. dw/adapter_compatibility.py +226 -0
  9. dw/arguments.py +1231 -0
  10. dw/assessment_rules.py +159 -0
  11. dw/assets.py +130 -0
  12. dw/cache_blocks.json +16 -0
  13. dw/cache_blocks.py +146 -0
  14. dw/community_pipelines/pipeline_flux_rf_inversion.py +1184 -0
  15. dw/content_types.py +150 -0
  16. dw/dissolve_frame_errors.py +121 -0
  17. dw/docs/ACCELERATION.md +352 -0
  18. dw/docs/AGENT_LOOP.md +95 -0
  19. dw/docs/DEPENDENCIES.md +91 -0
  20. dw/docs/IP_ADAPTER.md +109 -0
  21. dw/docs/LORAS.md +131 -0
  22. dw/docs/MCP.md +517 -0
  23. dw/docs/PROMPT_WEIGHTING.md +78 -0
  24. dw/docs/QUANTIZATION.md +230 -0
  25. dw/docs/RECIPES_24GB.md +201 -0
  26. dw/docs/RELEASING.md +195 -0
  27. dw/docs/REMOTE.md +140 -0
  28. dw/docs/REPL_COMMANDS.md +121 -0
  29. dw/docs/REPL_WORKER_GUIDE.md +51 -0
  30. dw/docs/SECURITY.md +272 -0
  31. dw/docs/SECURITY_QUICKREF.md +112 -0
  32. dw/docs/SERVER.md +679 -0
  33. dw/docs/TASKS.md +1741 -0
  34. dw/docs/TESTING.md +71 -0
  35. dw/docs/WORKFLOW_GUIDE.md +2038 -0
  36. dw/docs/WORKSPACES.md +316 -0
  37. dw/download_watch.py +335 -0
  38. dw/elision.py +306 -0
  39. dw/events.py +275 -0
  40. dw/for_each.py +409 -0
  41. dw/host_memory.py +258 -0
  42. dw/host_memory_projection.py +230 -0
  43. dw/hub_cache.py +432 -0
  44. dw/introspection.py +1228 -0
  45. dw/kernel_availability.py +208 -0
  46. dw/locations.py +599 -0
  47. dw/log_setup.py +45 -0
  48. dw/loudness.py +82 -0
  49. dw/media_audio.py +217 -0
  50. dw/media_frames.py +367 -0
  51. dw/media_info.py +297 -0
  52. dw/pipeline_processors/chain.py +821 -0
  53. dw/pipeline_processors/config_objects.py +237 -0
  54. dw/pipeline_processors/pipeline.py +2297 -0
  55. dw/pipeline_processors/remote.py +46 -0
  56. dw/plan.py +920 -0
  57. dw/previous_results.py +411 -0
  58. dw/probe_paths.py +59 -0
  59. dw/prompt_schema.json +48 -0
  60. dw/prompt_weighting.py +378 -0
  61. dw/prompts.py +159 -0
  62. dw/realize.py +250 -0
  63. dw/reference_limits.py +215 -0
  64. dw/reference_names.py +125 -0
  65. dw/repl.py +338 -0
  66. dw/repl_commands.py +836 -0
  67. dw/repl_worker.py +159 -0
  68. dw/result.py +1720 -0
  69. dw/result_fps.py +82 -0
  70. dw/run.py +162 -0
  71. dw/runs.py +768 -0
  72. dw/scalar_result_validation.py +97 -0
  73. dw/schema.py +283 -0
  74. dw/security.py +1038 -0
  75. dw/select_validation.py +115 -0
  76. dw/serve.py +277 -0
  77. dw/server/__init__.py +2 -0
  78. dw/server/app.py +4586 -0
  79. dw/server/assess.py +132 -0
  80. dw/server/catalog_shape.py +487 -0
  81. dw/server/enhancers.py +129 -0
  82. dw/server/exports.py +480 -0
  83. dw/server/guides.py +257 -0
  84. dw/server/jobs.py +1561 -0
  85. dw/server/mcp_mount.py +95 -0
  86. dw/server/netinfo.py +124 -0
  87. dw/server/observed_cost.py +379 -0
  88. dw/server/sysinfo.py +71 -0
  89. dw/server/ui/assets/abap-08VXUWAP.js +1 -0
  90. dw/server/ui/assets/apex-BWPQTe0t.js +1 -0
  91. dw/server/ui/assets/azcli-Bc_sGQ0U.js +1 -0
  92. dw/server/ui/assets/bat-i0X4ZdIN.js +1 -0
  93. dw/server/ui/assets/bicep-B5-_aFwp.js +2 -0
  94. dw/server/ui/assets/cameligo-DMUM7wLl.js +1 -0
  95. dw/server/ui/assets/clojure-Cm7r79vr.js +1 -0
  96. dw/server/ui/assets/codicon-Brq4_Ui5.ttf +0 -0
  97. dw/server/ui/assets/coffee-Ba7i2nA0.js +1 -0
  98. dw/server/ui/assets/cpp-C7h46wYY.js +1 -0
  99. dw/server/ui/assets/csharp-BKxtCVv1.js +1 -0
  100. dw/server/ui/assets/csp-bTuwJoIa.js +1 -0
  101. dw/server/ui/assets/css-DIMkf-bt.js +3 -0
  102. dw/server/ui/assets/css.worker-B3ciXF_0.js +93 -0
  103. dw/server/ui/assets/cssMode-CPznxfY8.js +1 -0
  104. dw/server/ui/assets/cypher-CVaqCwHa.js +1 -0
  105. dw/server/ui/assets/dart-onAF5SnQ.js +1 -0
  106. dw/server/ui/assets/dockerfile-DZFCIeNp.js +1 -0
  107. dw/server/ui/assets/ecl-D05T4iGw.js +1 -0
  108. dw/server/ui/assets/editor-jjEx9u7D.css +1 -0
  109. dw/server/ui/assets/editor.api-CpWcotrd.js +847 -0
  110. dw/server/ui/assets/editor.worker-q-txB4vs.js +30 -0
  111. dw/server/ui/assets/elixir-6RTg0lbw.js +1 -0
  112. dw/server/ui/assets/flow9-C5_-GSwl.js +1 -0
  113. dw/server/ui/assets/freemarker2-CXtRM8N4.js +3 -0
  114. dw/server/ui/assets/fsharp-C8Ef5oNN.js +1 -0
  115. dw/server/ui/assets/go-C-y9NEjX.js +1 -0
  116. dw/server/ui/assets/graphql-fmXr3nnJ.js +1 -0
  117. dw/server/ui/assets/handlebars-N7x-6NMY.js +1 -0
  118. dw/server/ui/assets/hcl-CpzslTdj.js +1 -0
  119. dw/server/ui/assets/html-PhsdjHSr.js +1 -0
  120. dw/server/ui/assets/html.worker-C93Ht9o9.js +506 -0
  121. dw/server/ui/assets/htmlMode-Dgj0SEok.js +1 -0
  122. dw/server/ui/assets/index-3Vw6WAPW.css +1 -0
  123. dw/server/ui/assets/index-DgrYhQd9.js +43 -0
  124. dw/server/ui/assets/ini-sBoK_t0W.js +1 -0
  125. dw/server/ui/assets/java-BEtHBSE6.js +1 -0
  126. dw/server/ui/assets/javascript-BJqN9Qhv.js +1 -0
  127. dw/server/ui/assets/json.worker-B2V3pomh.js +62 -0
  128. dw/server/ui/assets/jsonMode-DbM4SWSv.js +7 -0
  129. dw/server/ui/assets/julia-Bri6UV-V.js +1 -0
  130. dw/server/ui/assets/kotlin-BOotOW0E.js +1 -0
  131. dw/server/ui/assets/less-B9JPFI3C.js +2 -0
  132. dw/server/ui/assets/lexon-CfSJPG6W.js +1 -0
  133. dw/server/ui/assets/liquid-BWr8lEc4.js +1 -0
  134. dw/server/ui/assets/lspLanguageFeatures-C1iGuDyZ.js +4 -0
  135. dw/server/ui/assets/lua-CsQS60Ue.js +1 -0
  136. dw/server/ui/assets/m3-D-oSqn_W.js +1 -0
  137. dw/server/ui/assets/markdown-Cimd5fb3.js +1 -0
  138. dw/server/ui/assets/mdx-DAdMi_0p.js +1 -0
  139. dw/server/ui/assets/mips-CIPQ_RoX.js +1 -0
  140. dw/server/ui/assets/monaco--ixms01u.css +1 -0
  141. dw/server/ui/assets/monaco-BGCeEqaw.js +56 -0
  142. dw/server/ui/assets/msdax-DauUninz.js +1 -0
  143. dw/server/ui/assets/mysql-SOo6toE5.js +1 -0
  144. dw/server/ui/assets/objective-c-FvmIjYaQ.js +1 -0
  145. dw/server/ui/assets/pascal-DrH0SRf2.js +1 -0
  146. dw/server/ui/assets/pascaligo-D-ptJ9y-.js +1 -0
  147. dw/server/ui/assets/perl-oz_6vUea.js +1 -0
  148. dw/server/ui/assets/pgsql-DTj74zXo.js +1 -0
  149. dw/server/ui/assets/php-nr791fC2.js +1 -0
  150. dw/server/ui/assets/pla-CopQ2nXW.js +1 -0
  151. dw/server/ui/assets/postiats-43DmfD33.js +1 -0
  152. dw/server/ui/assets/powerquery-D3hlyOfw.js +1 -0
  153. dw/server/ui/assets/powershell-DmHpPYUd.js +1 -0
  154. dw/server/ui/assets/protobuf-C531GsRP.js +2 -0
  155. dw/server/ui/assets/pug-Z5eAx3Zn.js +1 -0
  156. dw/server/ui/assets/python-Bcn70HdC.js +1 -0
  157. dw/server/ui/assets/qsharp-DkqhCAOL.js +1 -0
  158. dw/server/ui/assets/r-BwWrilGY.js +1 -0
  159. dw/server/ui/assets/razor-D1HmNnby.js +1 -0
  160. dw/server/ui/assets/redis-ClamHrr6.js +1 -0
  161. dw/server/ui/assets/redshift-DT7zqm-g.js +1 -0
  162. dw/server/ui/assets/restructuredtext-BYgofb2h.js +1 -0
  163. dw/server/ui/assets/ruby-DezsRK8O.js +1 -0
  164. dw/server/ui/assets/rust-DdL9SqIa.js +1 -0
  165. dw/server/ui/assets/sb-CcwsVR0C.js +1 -0
  166. dw/server/ui/assets/scala-DHpiXF5c.js +1 -0
  167. dw/server/ui/assets/scheme-BeGwcela.js +1 -0
  168. dw/server/ui/assets/scss-gp-XZpBa.js +3 -0
  169. dw/server/ui/assets/shell-CC2rA5mh.js +1 -0
  170. dw/server/ui/assets/solidity-BEEn4gHE.js +1 -0
  171. dw/server/ui/assets/sophia-CRfGWb83.js +1 -0
  172. dw/server/ui/assets/sparql-D_Lu-MrJ.js +1 -0
  173. dw/server/ui/assets/sql-NEE52Syq.js +1 -0
  174. dw/server/ui/assets/st-DbInun42.js +1 -0
  175. dw/server/ui/assets/swift-Bxkupp3x.js +1 -0
  176. dw/server/ui/assets/systemverilog-Bz4Y3fRF.js +1 -0
  177. dw/server/ui/assets/tcl-DISqw1ZD.js +1 -0
  178. dw/server/ui/assets/ts.worker-D7T1-Ig5.js +67738 -0
  179. dw/server/ui/assets/tsMode-D6u0XmOW.js +11 -0
  180. dw/server/ui/assets/twig-De2hgUGE.js +1 -0
  181. dw/server/ui/assets/typescript-BU6v-LMV.js +1 -0
  182. dw/server/ui/assets/typespec-B8J7ngcE.js +1 -0
  183. dw/server/ui/assets/vb-DV3o63ZY.js +1 -0
  184. dw/server/ui/assets/wgsl-DpFanUEy.js +298 -0
  185. dw/server/ui/assets/workers-Cn7cTUKr.js +1 -0
  186. dw/server/ui/assets/xml--0LP2Lwk.js +1 -0
  187. dw/server/ui/assets/yaml-mpBg9jnt.js +1 -0
  188. dw/server/ui/index.html +17 -0
  189. dw/server/updater.py +192 -0
  190. dw/settings.py +98 -0
  191. dw/shot_span_preflight.py +116 -0
  192. dw/shots.py +359 -0
  193. dw/slice_preflight.py +148 -0
  194. dw/step.py +187 -0
  195. dw/step_cache.py +442 -0
  196. dw/subfolders.py +107 -0
  197. dw/task_domains.py +307 -0
  198. dw/tasks/assess.py +826 -0
  199. dw/tasks/audio_transcription.py +88 -0
  200. dw/tasks/audio_utils.py +1862 -0
  201. dw/tasks/background_remover.py +43 -0
  202. dw/tasks/borders.py +113 -0
  203. dw/tasks/compose_text.py +74 -0
  204. dw/tasks/concat_videos.py +300 -0
  205. dw/tasks/depth_estimator.py +54 -0
  206. dw/tasks/diffusion_upscale.py +109 -0
  207. dw/tasks/dissolve_videos.py +342 -0
  208. dw/tasks/format_messages.py +24 -0
  209. dw/tasks/gather.py +173 -0
  210. dw/tasks/grade.py +97 -0
  211. dw/tasks/image_to_text.py +43 -0
  212. dw/tasks/image_utils.py +764 -0
  213. dw/tasks/interpolate_frames.py +252 -0
  214. dw/tasks/judge.py +68 -0
  215. dw/tasks/model_cache.py +55 -0
  216. dw/tasks/pair_audio.py +268 -0
  217. dw/tasks/qr_code.py +19 -0
  218. dw/tasks/restore_faces.py +175 -0
  219. dw/tasks/rife_model.py +192 -0
  220. dw/tasks/segment.py +121 -0
  221. dw/tasks/select.py +111 -0
  222. dw/tasks/speech_generation.py +228 -0
  223. dw/tasks/stabilize.py +129 -0
  224. dw/tasks/task.py +920 -0
  225. dw/tasks/tensor_image.py +57 -0
  226. dw/tasks/text_generation.py +169 -0
  227. dw/tasks/text_sections.py +80 -0
  228. dw/tasks/upscale.py +203 -0
  229. dw/tasks/video_utils.py +624 -0
  230. dw/tasks/zoe_depth.py +71 -0
  231. dw/teacache.py +381 -0
  232. dw/teacache_models.json +99 -0
  233. dw/test.py +29 -0
  234. dw/type_helpers.py +231 -0
  235. dw/validate.py +68 -0
  236. dw/variable_constraints.py +444 -0
  237. dw/variables.py +443 -0
  238. dw/video_extensions.py +141 -0
  239. dw/vram_estimate.py +116 -0
  240. dw/worker.py +764 -0
  241. dw/workflow.py +2007 -0
  242. dw/workflow_schema.json +1346 -0
  243. dw/workflow_sources.py +383 -0
  244. dw/workflows/h3_context_ir.json +57 -0
  245. dw/workflows/test.json +31 -0
  246. dw/workspace.py +730 -0
  247. dw_mcp/__init__.py +6 -0
  248. dw_mcp/__main__.py +133 -0
  249. dw_mcp/assets.py +336 -0
  250. dw_mcp/authoring.py +114 -0
  251. dw_mcp/catalog.py +360 -0
  252. dw_mcp/client.py +486 -0
  253. dw_mcp/diagnose.py +371 -0
  254. dw_mcp/exports.py +84 -0
  255. dw_mcp/guides.py +35 -0
  256. dw_mcp/media.py +638 -0
  257. dw_mcp/models.py +97 -0
  258. dw_mcp/prompts.py +104 -0
  259. dw_mcp/server.py +1343 -0
  260. 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()