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/result_fps.py ADDED
@@ -0,0 +1,82 @@
1
+ """A step's result 'fps': the frame rate a video is written at.
2
+
3
+ Widened the same way `subfolder` was (dw/subfolders.py): the schema lets the
4
+ raw document hold a `variable:` or `item:` reference so a workflow can keep
5
+ one frame-rate variable rather than a literal duplicated between the step
6
+ that generates and the step that writes (#363). This module owns the
7
+ resolved-value check: once substitution has run, `result.fps` has to be a
8
+ real, positive frame rate, and it must be a whole number - `encode_video`
9
+ (dw/result.py, the writer used whenever a step's result carries audio) hands
10
+ the value straight to PyAV as `rate=int(fps)`, so a fractional rate like
11
+ 23.976 would be silently floored to 23 rather than written as asked. A
12
+ frame_rate variable declared as a float (24.0) still passes, since it carries
13
+ no fractional part; only a genuine fraction is refused.
14
+
15
+ Containment doesn't apply here - there's no path to join - so unlike
16
+ subfolder_errors this only ever checks value shape.
17
+ """
18
+
19
+ from .for_each import MEMBER_SEPARATOR, render_path
20
+
21
+ FPS_KEY = "fps"
22
+
23
+ # Reference prefixes substitution resolves before this pass runs. One still
24
+ # spelled out here is one nothing resolved, and that is the undeclared-
25
+ # variable pass's complaint rather than a shape error
26
+ _UNRESOLVED_PREFIXES = ("variable:", "item:")
27
+
28
+
29
+ def fps_errors(workflow_definition, source_indices=None):
30
+ """Every result 'fps' that cannot be written, as [{path, message}].
31
+
32
+ The definition handed here has already been substituted and expanded,
33
+ so every value in it is literal; a 'variable:' or 'item:' still spelled
34
+ out is left alone. `source_indices`, when given, is the source step
35
+ index of each step - a 'for_each' group turns one written step into
36
+ several, and the path an error carries has to be one the author can
37
+ find in the file they wrote; the member is named in the message.
38
+ """
39
+ steps = workflow_definition.get("steps")
40
+ if not isinstance(steps, list):
41
+ return []
42
+
43
+ errors = []
44
+ for index, step in enumerate(steps):
45
+ if not isinstance(step, dict):
46
+ continue
47
+ result = step.get("result")
48
+ if not isinstance(result, dict) or FPS_KEY not in result:
49
+ continue
50
+ value = result[FPS_KEY]
51
+ if isinstance(value, str) and value.startswith(_UNRESOLVED_PREFIXES):
52
+ continue
53
+
54
+ source = (
55
+ source_indices[index]
56
+ if source_indices is not None and index < len(source_indices)
57
+ else index
58
+ )
59
+ name = step.get("name")
60
+ where = (
61
+ f" in member '{name}'"
62
+ if isinstance(name, str) and MEMBER_SEPARATOR in name
63
+ else ""
64
+ )
65
+ message = None
66
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
67
+ message = f"Invalid fps: {value!r} - fps is a number of frames per second"
68
+ elif value <= 0:
69
+ message = f"Invalid fps: {value!r} - fps must be greater than zero"
70
+ elif float(value) != int(value):
71
+ message = (
72
+ f"Invalid fps: {value!r} - fps must be a whole number; the video "
73
+ f"writer truncates a fractional rate rather than honoring it"
74
+ )
75
+ if message is not None:
76
+ errors.append(
77
+ {
78
+ "path": render_path(("steps", source, "result", FPS_KEY)),
79
+ "message": f"{message}{where}",
80
+ }
81
+ )
82
+ return errors
dw/run.py ADDED
@@ -0,0 +1,162 @@
1
+ import argparse
2
+ import os
3
+ from . import startup
4
+ from .workflow import workflow_from_file
5
+ from .runs import set_output_layout
6
+ from .workspace import resolve_workspace, set_workspace
7
+ from .security import (
8
+ validate_workflow_path,
9
+ validate_output_path,
10
+ validate_variable_name,
11
+ validate_string_input,
12
+ set_trust_workflows,
13
+ SecurityError,
14
+ MAX_VARIABLE_VALUE_LENGTH,
15
+ )
16
+
17
+
18
+ def main():
19
+ parser = argparse.ArgumentParser(description="Run a workflow from a file.")
20
+ parser.add_argument(
21
+ "file_name", type=str, help="The filespec to of the workflow to run"
22
+ )
23
+ parser.add_argument(
24
+ "-o",
25
+ "--output_dir",
26
+ type=str,
27
+ default=None,
28
+ help="The folder to write the outputs to (default: the workspace's "
29
+ "outputs/ - ./outputs when run from a workspace, as a checkout is)",
30
+ )
31
+ parser.add_argument(
32
+ "--workspace",
33
+ type=str,
34
+ default=None,
35
+ help="Directory holding your workflows, prompts, assets and outputs "
36
+ "(default: DW_WORKSPACE, else the 'workspace' setting, else the "
37
+ "working directory when it looks like a workspace, else "
38
+ "~/diffusers-workspace)",
39
+ )
40
+ parser.add_argument(
41
+ "variables",
42
+ nargs="*", # Accept 0 or more parameters
43
+ help="Optional parameters in name=value format",
44
+ )
45
+ parser.add_argument(
46
+ "-l",
47
+ "--log_level",
48
+ type=str,
49
+ default="INFO",
50
+ help="Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
51
+ )
52
+ parser.add_argument(
53
+ "--prompt-dir",
54
+ type=str,
55
+ default=None,
56
+ help="Directory 'prompt:' references resolve against (default: "
57
+ "DW_PROMPT_DIR, else ./prompts if it exists, else the nearest "
58
+ "prompts/ above the workflow file)",
59
+ )
60
+ parser.add_argument(
61
+ "--asset-dir",
62
+ type=str,
63
+ default=None,
64
+ help="Directory 'asset:' references resolve against (default: "
65
+ "DW_ASSET_DIR, else the workspace's assets/ when a workspace was "
66
+ "named, else ./assets if it exists, else the nearest assets/ above "
67
+ "the workflow file)",
68
+ )
69
+ parser.add_argument(
70
+ "--output-layout",
71
+ type=str,
72
+ choices=("run", "flat"),
73
+ default=None,
74
+ help="'run' (default) gives each run its own directory under "
75
+ "<output_dir>/<workflow>/, with a manifest.json beside its files; "
76
+ "'flat' writes the way it did before run directories",
77
+ )
78
+ parser.add_argument(
79
+ "--trust-workflows",
80
+ action="store_true",
81
+ default=False,
82
+ help="Trust this workflow file to execute arbitrary Python: allow "
83
+ "pre_load_modules and any dotted *_type/*_dtype/dtype/config_type "
84
+ "value, not just ones inside the diffusers/torch/transformers/"
85
+ "quantization-backend ecosystem the tool already depends on. Off "
86
+ "by default - see docs/SECURITY.md's Trust model. Only pass this "
87
+ "for a workflow file whose source you trust.",
88
+ )
89
+ # parse_intermixed_args, not parse_args: argparse splits positionals into
90
+ # groups around an option, and with a nargs='*' positional that means
91
+ # 'dw-run wf.json -o outputs prompt=cat' - an option between the file
92
+ # and the name=value pairs - fails as unrecognized arguments
93
+ args = parser.parse_intermixed_args()
94
+
95
+ # Pinned before anything resolves a directory from it, and exported so a
96
+ # subprocess sees the same answer
97
+ workspace = set_workspace(resolve_workspace(args.workspace))
98
+ output_dir = args.output_dir or workspace.outputs
99
+
100
+ if args.prompt_dir:
101
+ os.environ["DW_PROMPT_DIR"] = os.path.abspath(args.prompt_dir)
102
+
103
+ if args.asset_dir:
104
+ os.environ["DW_ASSET_DIR"] = os.path.abspath(args.asset_dir)
105
+
106
+ if args.output_layout:
107
+ set_output_layout(args.output_layout)
108
+
109
+ set_trust_workflows(args.trust_workflows)
110
+
111
+ # Parse key-value pairs with validation
112
+ variables = {}
113
+ for variable in args.variables:
114
+ try:
115
+ name, value = variable.split("=", 1)
116
+ # Validate variable name and value
117
+ validated_name = validate_variable_name(name.strip())
118
+ validated_value = validate_string_input(
119
+ value.strip(), max_length=MAX_VARIABLE_VALUE_LENGTH, allow_empty=True
120
+ )
121
+ variables[validated_name] = validated_value
122
+ except ValueError:
123
+ print(f"Error: Variable '{variable}' is not in name=value format")
124
+ exit(1)
125
+ except SecurityError as e:
126
+ print(f"Error: Invalid variable input: {e}")
127
+ exit(1)
128
+
129
+ # Validate and secure file paths
130
+ try:
131
+ validated_output_dir = validate_output_path(output_dir, None)
132
+ if not os.path.exists(validated_output_dir):
133
+ # Create output directory if it doesn't exist
134
+ os.makedirs(validated_output_dir, exist_ok=True)
135
+ print(f"Created output directory: {validated_output_dir}")
136
+
137
+ validated_file_path = validate_workflow_path(args.file_name)
138
+ if not os.path.exists(validated_file_path):
139
+ raise FileNotFoundError(f"File {validated_file_path} does not exist")
140
+
141
+ except SecurityError as e:
142
+ print(f"Error: Security validation failed: {e}")
143
+ exit(1)
144
+
145
+ startup(args.log_level)
146
+
147
+ workflow = workflow_from_file(validated_file_path, validated_output_dir)
148
+ try:
149
+ workflow.validate()
150
+ except Exception as e:
151
+ print(f"Error validating workflow '{args.file_name}': {e}")
152
+ exit(1)
153
+
154
+ try:
155
+ workflow.run(variables)
156
+ except Exception as e:
157
+ print(f"Error running workflow '{args.file_name}': {e}")
158
+ exit(1)
159
+
160
+
161
+ if __name__ == "__main__":
162
+ main()