flowweave 2.0.1__py3-none-any.whl → 3.0.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.
flowweave/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
- __version__ = "2.0.1"
1
+ __version__ = "3.0.0"
2
2
  __author__ = "syatch"
3
3
  __license__ = "MIT"
4
4
 
5
- from .flowweave import FlowWeave, FlowWeaveTask
6
- from .base import Result, TaskData, FlowWeaveTaskRunner
5
+ from .flowweave import FlowWeave
6
+ from .base import FlowWeaveResult, FlowWeaveTask
flowweave/base.py CHANGED
@@ -4,7 +4,7 @@ from typing import IO, Optional
4
4
 
5
5
  from colorama import Fore
6
6
 
7
- class Result(IntEnum):
7
+ class FlowWeaveResult(IntEnum):
8
8
  FAIL = 0
9
9
  SUCCESS = 1
10
10
  IGNORE = 2
@@ -30,17 +30,17 @@ class TaskData:
30
30
  self.do_only = do_only
31
31
  self.show_log = show_log
32
32
 
33
- class FlowWeaveTaskRunner:
33
+ class FlowWeaveTask:
34
34
  def __init__(self, prev_future):
35
35
  self.prev_future = prev_future
36
36
  self.return_data = None
37
37
 
38
- def __call__(self) -> Result:
39
- result = self.run()
40
- return result
38
+ def __call__(self):
39
+ result, return_data = self.run()
40
+ return result, return_data
41
41
 
42
- def run(self) -> Result:
43
- return Result.SUCCESS
42
+ def run(self):
43
+ return FlowWeaveResult.SUCCESS, self.return_data
44
44
 
45
45
  def set_task_data(self, task_data: TaskData) -> None:
46
46
  self.task_data = task_data
flowweave/cli.py CHANGED
@@ -8,7 +8,7 @@ import colorama
8
8
 
9
9
  # Local application / relative imports
10
10
  from .flowweave import FlowWeave
11
- from .base import Result
11
+ from .base import FlowWeaveResult
12
12
 
13
13
  def get_setting_path(args):
14
14
  setting_path = None
@@ -59,7 +59,7 @@ def build_parser() -> argparse.ArgumentParser:
59
59
  return parser
60
60
 
61
61
  def main() -> None:
62
- result = Result.SUCCESS
62
+ result = FlowWeaveResult.SUCCESS
63
63
 
64
64
  colorama.init(autoreset=True)
65
65
 
@@ -72,7 +72,7 @@ def main() -> None:
72
72
  if not setting_path:
73
73
  parser.error("run requires flow_file")
74
74
  results = FlowWeave.run(setting_file=setting_path, parallel=args.parallel, show_log = args.log)
75
- result = all(x == Result.SUCCESS for x in results)
75
+ result = all(x == FlowWeaveResult.SUCCESS for x in results)
76
76
  elif args.command == "info":
77
77
  if args.flow_file:
78
78
  show_flow_op(setting_path, args.flow_file, info=True)
flowweave/flowweave.py CHANGED
@@ -1,7 +1,9 @@
1
1
  # Standard library
2
2
  import copy
3
+ from functools import reduce
3
4
  import importlib
4
5
  from importlib.resources import files
6
+ import inspect
5
7
  import itertools
6
8
  import json
7
9
  import logging
@@ -14,7 +16,7 @@ import yaml
14
16
  from prefect import flow, task, get_run_logger
15
17
 
16
18
  # Local application / relative imports
17
- from .base import Result, TaskData
19
+ from .base import FlowWeaveResult, TaskData, FlowWeaveTask
18
20
  from .message import FlowMessage
19
21
 
20
22
  class StageData():
@@ -38,15 +40,13 @@ class StageData():
38
40
  text += "==========="
39
41
  return text
40
42
 
41
- class FlowWeaveTask():
42
- task_class = None
43
-
43
+ class TaskRunner():
44
44
  @task
45
45
  def start(prev_future, task_data: TaskData):
46
46
  try:
47
- task_instance = task_data.task_class.runner(prev_future)
47
+ task_instance = task_data.task_class(prev_future)
48
48
  except AttributeError:
49
- raise TypeError(f"{task_data.task_class} must define runner")
49
+ raise TypeError(f"Failed to get instance of '{task_data.task_class}'")
50
50
 
51
51
  # set task member variables
52
52
  setattr(task_instance, "task_data", task_data)
@@ -62,23 +62,23 @@ class FlowWeaveTask():
62
62
  run_task = True
63
63
  if prev_future:
64
64
  if "pre_success" == task_data.do_only:
65
- run_task = True if (Result.SUCCESS == prev_future.get("result")) else False
65
+ run_task = True if (FlowWeaveResult.SUCCESS == prev_future.get("result")) else False
66
66
  elif "pre_fail" == task_data.do_only:
67
- run_task = True if (Result.FAIL == prev_future.get("result")) else False
67
+ run_task = True if (FlowWeaveResult.FAIL == prev_future.get("result")) else False
68
68
 
69
69
  if run_task:
70
- FlowWeaveTask.message_task_start(prev_future, task_data)
70
+ TaskRunner.message_task_start(prev_future, task_data)
71
71
 
72
72
  try:
73
73
  task_result, return_data = task_instance()
74
74
  except Exception as e:
75
75
  FlowMessage.error(e)
76
- task_result = Result.FAIL
76
+ task_result = FlowWeaveResult.FAIL
77
77
 
78
78
  FlowMessage.task_end(task_data, task_result)
79
79
  else:
80
- FlowWeaveTask.message_task_ignore(prev_future, task_data)
81
- task_result = Result.IGNORE
80
+ TaskRunner.message_task_ignore(prev_future, task_data)
81
+ task_result = FlowWeaveResult.IGNORE
82
82
 
83
83
  return {"name" : task_data.name, "option" : task_data.option, "data" : return_data, "result" : task_result}
84
84
 
@@ -207,27 +207,43 @@ class FlowWeave():
207
207
  op_dic = setting.get("op", {})
208
208
  for op, op_info in op_dic.items():
209
209
  script_name = op_info.get('script')
210
- op_class = FlowWeave._get_op_class(source_name, script_name, info)
210
+ op_class = FlowWeave._get_op_class(source_name, script_name, FlowWeaveTask)
211
211
 
212
212
  return_dic[str(op)] = op_class
213
213
 
214
214
  return return_dic
215
215
 
216
- def _get_op_class(source_name: str, script_name: str, info: bool = False):
216
+ def _get_op_class(source_name: str, script_name: str, base_class):
217
217
  module_name = f"{source_name}.{script_name}"
218
+
218
219
  try:
219
220
  module = importlib.import_module(module_name)
220
221
  except Exception as e:
221
222
  raise RuntimeError(f"Failed to import {module_name}: {e}")
222
223
 
223
- if not hasattr(module, "Task"):
224
- raise RuntimeError(f"'Task' class not found in {module_name}")
224
+ candidates = []
225
+
226
+ for _, obj in inspect.getmembers(module, inspect.isclass):
227
+ if obj.__module__ != module.__name__:
228
+ continue
225
229
 
226
- return_module = module.Task
227
- if info:
228
- return_module = module.Task.runner
230
+ if issubclass(obj, base_class) and obj is not base_class:
231
+ candidates.append(obj)
229
232
 
230
- return return_module
233
+ if len(candidates) == 0:
234
+ raise RuntimeError(
235
+ f"No subclass of {base_class.__name__} found in {module_name}"
236
+ )
237
+
238
+ if len(candidates) > 1:
239
+ names = ", ".join(c.__name__ for c in candidates)
240
+ raise RuntimeError(
241
+ f"Multiple subclasses of {base_class.__name__} found in {module_name}: {names}"
242
+ )
243
+
244
+ cls = candidates[0]
245
+
246
+ return cls
231
247
 
232
248
  def _get_global_option_comb(global_option: dict) -> list:
233
249
  keys = list(global_option.keys())
@@ -247,7 +263,7 @@ class FlowWeave():
247
263
 
248
264
  @task
249
265
  def run_flow(flow_data: dict, global_cmb: dict, op_dic: dict, part: int, all: int, show_log: bool = False) -> list[str]:
250
- flow_result = Result.SUCCESS
266
+ flow_result = FlowWeaveResult.SUCCESS
251
267
 
252
268
  if show_log:
253
269
  text = "= Flow =\n"
@@ -269,8 +285,8 @@ class FlowWeave():
269
285
  FlowWeave._print_log(str(stage_data))
270
286
 
271
287
  result = FlowWeave._run_stage(stage_data, show_log)
272
- if Result.FAIL == result:
273
- flow_result = Result.FAIL
288
+ if FlowWeaveResult.FAIL == result:
289
+ flow_result = FlowWeaveResult.FAIL
274
290
 
275
291
  FlowMessage.stage_end(stage, part, all, flow_result)
276
292
 
@@ -293,7 +309,7 @@ class FlowWeave():
293
309
  logger.info(f"{text}")
294
310
 
295
311
  def _run_stage(stage_data: StageData, show_log: bool = False):
296
- stage_result = Result.SUCCESS
312
+ stage_result = FlowWeaveResult.SUCCESS
297
313
 
298
314
  all_futures = []
299
315
 
@@ -306,11 +322,23 @@ class FlowWeave():
306
322
 
307
323
  for f in all_futures:
308
324
  result = f.result()
309
- if Result.FAIL == result.get("result"):
310
- stage_result = Result.FAIL
325
+ if FlowWeaveResult.FAIL == result.get("result"):
326
+ stage_result = FlowWeaveResult.FAIL
311
327
 
312
328
  return stage_result
313
329
 
330
+ def _deep_merge(a: dict, b: dict) -> dict:
331
+ result = a.copy()
332
+ for k, v in b.items():
333
+ if k in result and isinstance(result[k], dict) and isinstance(v, dict):
334
+ result[k] = FlowWeave._deep_merge(result[k], v)
335
+ else:
336
+ result[k] = v
337
+ return result
338
+
339
+ def _deep_merge_many(*dicts):
340
+ return reduce(FlowWeave._deep_merge, dicts)
341
+
314
342
  def _run_task(stage_data: dict, task_name: str, prev_future = None, visited = None, show_log: bool = False):
315
343
  if visited is None:
316
344
  visited = set()
@@ -326,11 +354,7 @@ class FlowWeave():
326
354
 
327
355
  default_option = stage_data.default_option or {}
328
356
  global_option = stage_data.global_option or {}
329
- task_option = copy.deepcopy(
330
- default_option
331
- | global_option
332
- | task_dic.get("option", {})
333
- )
357
+ task_option = FlowWeave._deep_merge_many(default_option, global_option, task_dic.get("option", {}))
334
358
 
335
359
  task_data = TaskData(name=task_name,
336
360
  task_class=task_module,
@@ -341,9 +365,9 @@ class FlowWeave():
341
365
  do_only=task_dic.get("do_only"),
342
366
  show_log=show_log)
343
367
  if prev_future is None:
344
- future = task_module.start.submit(None, task_data)
368
+ future = TaskRunner.start.submit(None, task_data)
345
369
  else:
346
- future = task_module.start.submit(prev_future, task_data)
370
+ future = TaskRunner.start.submit(prev_future, task_data)
347
371
 
348
372
  links = task_dic.get("chain", {}).get("next", [])
349
373
  links = links if isinstance(links, list) else [links]
flowweave/message.py CHANGED
@@ -5,7 +5,7 @@ from typing import IO, Optional
5
5
  from colorama import Fore
6
6
 
7
7
  # Local application / relative imports
8
- from .base import Result, TaskData
8
+ from .base import FlowWeaveResult, TaskData
9
9
 
10
10
  class FlowMessage:
11
11
  @staticmethod
@@ -17,14 +17,14 @@ class FlowMessage:
17
17
  print(*args, sep=sep, end=end, file=file, flush=flush)
18
18
 
19
19
  @staticmethod
20
- def get_result_text(result: Result) -> str:
20
+ def get_result_text(result: FlowWeaveResult) -> str:
21
21
  text = ""
22
22
 
23
- if Result.SUCCESS == result:
23
+ if FlowWeaveResult.SUCCESS == result:
24
24
  text = f"{Fore.GREEN}SUCCESS"
25
- elif Result.IGNORE == result:
25
+ elif FlowWeaveResult.IGNORE == result:
26
26
  text = f"{Fore.CYAN}IGNORE"
27
- elif Result.FAIL == result:
27
+ elif FlowWeaveResult.FAIL == result:
28
28
  text = f"{Fore.RED}FAIL"
29
29
  else:
30
30
  text = f"{Fore.MAGENTA}UNKNOWN: {result.name}({result.value})"
@@ -42,7 +42,7 @@ class FlowMessage:
42
42
  FlowMessage._print(text)
43
43
 
44
44
  @staticmethod
45
- def flow_end(part: int, all: int, result: Result) -> None:
45
+ def flow_end(part: int, all: int, result: FlowWeaveResult) -> None:
46
46
  result_text = FlowMessage.get_result_text(result)
47
47
  text = f"{Fore.YELLOW}[Flow {part} / {all}] Finish - {result_text}"
48
48
  FlowMessage._print(text)
@@ -53,7 +53,7 @@ class FlowMessage:
53
53
  FlowMessage._print(text)
54
54
 
55
55
  @staticmethod
56
- def stage_end(stage: str, part: int, all: int, result: Result) -> None:
56
+ def stage_end(stage: str, part: int, all: int, result: FlowWeaveResult) -> None:
57
57
  result_text = FlowMessage.get_result_text(result)
58
58
  text = f"{Fore.MAGENTA}[Flow {part} / {all}] Finish Stage {stage} - {result_text}"
59
59
  FlowMessage._print(text)
@@ -79,7 +79,7 @@ class FlowMessage:
79
79
  FlowMessage._print(text)
80
80
 
81
81
  @staticmethod
82
- def task_end(task_data: TaskData, result: Result) -> None:
82
+ def task_end(task_data: TaskData, result: FlowWeaveResult) -> None:
83
83
  result_text = FlowMessage.get_result_text(result)
84
84
  text = f"{Fore.CYAN}[Flow {task_data.flow_part} / {task_data.flow_all}] Finish Task {task_data.stage_name}/{task_data.name} - {result_text}"
85
85
  FlowMessage._print(text)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowweave
3
- Version: 2.0.1
3
+ Version: 3.0.0
4
4
  Summary: YAML-based workflow runner for task orchestration
5
5
  Author: syatch
6
6
  License: MIT
@@ -0,0 +1,13 @@
1
+ flowweave/__init__.py,sha256=hFKSVXLXkc5VTBybQshM0N9uvhA02lAiCsImk-bp3K8,151
2
+ flowweave/base.py,sha256=o5spY8XxfoP585Qxw02o7xIz9phJUXnVjKXCLUmhsFk,2514
3
+ flowweave/cli.py,sha256=xO3hWlMlK9RLniXwQGHHq21IpX9PGki1n3UCR9VAvmQ,2651
4
+ flowweave/flowweave.py,sha256=5bnVxfrQ2NzW8pssHyz_OLwIRaB_3GcijyXmX3dKWAQ,14413
5
+ flowweave/message.py,sha256=2tvWW0rEQFjJFwb4Zu7ht2zb6iTRVLIux3jX3P2KzIU,3780
6
+ flowweave/schema/flow.json,sha256=K7EAcH2KqniAPdNJWJdLiOYC77D2SGg50SOU2frC7LU,1810
7
+ flowweave/schema/op_code.json,sha256=C9JeyBvSC6fA8Ss0exk3nsPjTRq_2XpK2gshvAcavbQ,414
8
+ flowweave-3.0.0.dist-info/licenses/LICENSE,sha256=iN7x3Cz45_nCPCn23daVPqs0m_ZWPvTdayE2jg2OSmY,1203
9
+ flowweave-3.0.0.dist-info/METADATA,sha256=sSjhmn-nA2LXYMT_ZMO8R6xWyhQDgvmxXZO04W-n5I8,580
10
+ flowweave-3.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
+ flowweave-3.0.0.dist-info/entry_points.txt,sha256=qV4hkGzamuf-N1eNz-h_tRQ89Wa_py2jIdOSTeGAE1M,49
12
+ flowweave-3.0.0.dist-info/top_level.txt,sha256=7fLs0F6CROwWhmjP9CyKODB25HJohoougiOCCL1YD_Y,10
13
+ flowweave-3.0.0.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- flowweave/__init__.py,sha256=x_q1GhaS-BORef1-l3gu0OB_Jk-cUc9R5M3RiYC-ZCc,173
2
- flowweave/base.py,sha256=By6Cz5HsFuGJFf-VxQqpIrrky86nLSMl6da88Xs9ku4,2478
3
- flowweave/cli.py,sha256=5w3HD5Gd3p8Tgcka9m2NH1wRgsyMgHs-C_Yoc3jRtPA,2624
4
- flowweave/flowweave.py,sha256=x6B5QOkA1sRoEILKePudb2GggXHt3bkPecVTWPrH2pI,13410
5
- flowweave/message.py,sha256=U2Kg-RaqAIzLKtnDG2LSMnsqjE1EO9s5H5qwByTQv9E,3708
6
- flowweave/schema/flow.json,sha256=K7EAcH2KqniAPdNJWJdLiOYC77D2SGg50SOU2frC7LU,1810
7
- flowweave/schema/op_code.json,sha256=C9JeyBvSC6fA8Ss0exk3nsPjTRq_2XpK2gshvAcavbQ,414
8
- flowweave-2.0.1.dist-info/licenses/LICENSE,sha256=iN7x3Cz45_nCPCn23daVPqs0m_ZWPvTdayE2jg2OSmY,1203
9
- flowweave-2.0.1.dist-info/METADATA,sha256=IubrZCx7SdrHK5qM9tKHqHQ7b6cI98FPb2TbwuRmh08,580
10
- flowweave-2.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
- flowweave-2.0.1.dist-info/entry_points.txt,sha256=qV4hkGzamuf-N1eNz-h_tRQ89Wa_py2jIdOSTeGAE1M,49
12
- flowweave-2.0.1.dist-info/top_level.txt,sha256=7fLs0F6CROwWhmjP9CyKODB25HJohoougiOCCL1YD_Y,10
13
- flowweave-2.0.1.dist-info/RECORD,,