codeflowhub 1.3.0__tar.gz → 1.4.0__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.
Files changed (33) hide show
  1. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/PKG-INFO +1 -1
  2. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/flow.py +15 -1
  3. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/service/airflow_exporter.py +7 -1
  4. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub.egg-info/PKG-INFO +1 -1
  5. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub.egg-info/SOURCES.txt +2 -1
  6. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/setup.py +1 -1
  7. codeflowhub-1.4.0/tests/test_flow_concurrency.py +55 -0
  8. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/LICENSE +0 -0
  9. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/README.md +0 -0
  10. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/__init__.py +0 -0
  11. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/action.py +0 -0
  12. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/airflow/__init__.py +0 -0
  13. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/airflow/xcom.py +0 -0
  14. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/base.py +0 -0
  15. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/model.py +0 -0
  16. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/service/__init__.py +0 -0
  17. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/storage/__init__.py +0 -0
  18. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/storage/local_storage.py +0 -0
  19. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/storage/s3_storage.py +0 -0
  20. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/storage/storage.py +0 -0
  21. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/task.py +0 -0
  22. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/template/__init__.py +0 -0
  23. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/template/analyze_speaker_pkg/__init__.py +0 -0
  24. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/template/analyze_speaker_pkg/main.py +0 -0
  25. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/template/extract_voice_pkg/__init__.py +0 -0
  26. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/template/extract_voice_pkg/main.py +0 -0
  27. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/template/read_pdf_pkg/__init__.py +0 -0
  28. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/template/read_pdf_pkg/main.py +0 -0
  29. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/template/transcript_pkg/__init__.py +0 -0
  30. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub/template/transcript_pkg/main.py +0 -0
  31. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub.egg-info/dependency_links.txt +0 -0
  32. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/codeflowhub.egg-info/top_level.txt +0 -0
  33. {codeflowhub-1.3.0 → codeflowhub-1.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeflowhub
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: workflow development tools
5
5
  Author: creaddiscans
6
6
  Author-email: creaddiscans@gmail.com
@@ -31,6 +31,8 @@ class FlowDecorator(BaseDecorator):
31
31
  default_args:dict # Airflow DAG default_args (owner, retries, retry_delay 등 override)
32
32
  schedule:str # Airflow DAG 스케줄 (cron 문자열 / preset / timedelta / None=수동 트리거)
33
33
  doc_md:str # Airflow DAG doc_md (UI에 표시될 Markdown 문서)
34
+ max_active_runs:int # DAG 동시 실행(run) 상한 (None=Airflow 기본값)
35
+ max_active_tasks:int # DAG 내 동시 실행 task 상한 (None=Airflow 기본값)
34
36
  start_date:datetime # Airflow DAG start_date (None이면 export 시점)
35
37
  secrets:list # 모든 task에 공통 주입할 K8s Secret(env) 목록
36
38
  on_failure: 'BaseDecorator' = None # 모든 task의 기본 failure handler
@@ -39,7 +41,8 @@ class FlowDecorator(BaseDecorator):
39
41
  tags=None, annotations=None, service_account_name=None, volumes=None,
40
42
  airflow_sidecar_image=None, airflow_connection_id=None, repo=None, path=None,
41
43
  export_destination='dags', default_args=None, secrets=None, on_failure=None,
42
- schedule=None, start_date=None, doc_md=None, schedule_interval=None, **kwargs):
44
+ schedule=None, start_date=None, doc_md=None, max_active_runs=None, max_active_tasks=None,
45
+ schedule_interval=None, **kwargs):
43
46
  # CLI 속성 먼저 초기화 (init()에서 사용됨)
44
47
  self._cli_export = None
45
48
  self._cli_job_dir = None
@@ -67,6 +70,8 @@ class FlowDecorator(BaseDecorator):
67
70
  )
68
71
  self.start_date = start_date
69
72
  self.doc_md = self._normalize_doc_md(doc_md)
73
+ self.max_active_runs = self._positive_int('max_active_runs', max_active_runs)
74
+ self.max_active_tasks = self._positive_int('max_active_tasks', max_active_tasks)
70
75
  # Design Ref: §3.2 — flow-level 공통 Secret(env)
71
76
  self.secrets = secrets or []
72
77
  self.on_failure = on_failure
@@ -77,6 +82,15 @@ class FlowDecorator(BaseDecorator):
77
82
  # super().__init__()는 마지막에 호출 (init()이 실행됨)
78
83
  super().__init__(*args, name=name, **kwargs)
79
84
 
85
+ @staticmethod
86
+ def _positive_int(name, value):
87
+ """None 또는 양의 정수만 허용 (선언 시점에 조기 실패)"""
88
+ if value is None:
89
+ return None
90
+ if isinstance(value, bool) or not isinstance(value, int) or value < 1:
91
+ raise ValueError(f'{name}는 1 이상의 정수 또는 None만 가능합니다: {value!r}')
92
+ return value
93
+
80
94
  @staticmethod
81
95
  def _normalize_doc_md(doc_md):
82
96
  """doc_md 정규화 (선언 시점에 조기 실패)
@@ -280,13 +280,19 @@ DAG_DOC_MD = {self._render_doc_md(doc_md)}
280
280
  '''
281
281
  doc_md_line = "\n doc_md=DAG_DOC_MD,"
282
282
 
283
+ concurrency_lines = ""
284
+ for key in ('max_active_runs', 'max_active_tasks'):
285
+ value = getattr(self.flow, key, None)
286
+ if value is not None:
287
+ concurrency_lines += f"\n {key}={value},"
288
+
283
289
  dag_definition = f'''{doc_md_definition}dag = DAG(
284
290
  '{self.dag_id}',
285
291
  default_args={default_args_str},
286
292
  description='{description}',
287
293
  schedule={self._render_schedule(schedule)},
288
294
  catchup=False,
289
- tags={tags_str},{params_line}{doc_md_line}
295
+ tags={tags_str},{params_line}{doc_md_line}{concurrency_lines}
290
296
  )
291
297
 
292
298
  '''
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeflowhub
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: workflow development tools
5
5
  Author: creaddiscans
6
6
  Author-email: creaddiscans@gmail.com
@@ -27,4 +27,5 @@ codeflowhub/template/extract_voice_pkg/main.py
27
27
  codeflowhub/template/read_pdf_pkg/__init__.py
28
28
  codeflowhub/template/read_pdf_pkg/main.py
29
29
  codeflowhub/template/transcript_pkg/__init__.py
30
- codeflowhub/template/transcript_pkg/main.py
30
+ codeflowhub/template/transcript_pkg/main.py
31
+ tests/test_flow_concurrency.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='codeflowhub',
5
- version='1.3.0',
5
+ version='1.4.0',
6
6
  description='workflow development tools',
7
7
  author='creaddiscans',
8
8
  author_email='creaddiscans@gmail.com',
@@ -0,0 +1,55 @@
1
+ """max_active_runs / max_active_tasks 검증·렌더 체크. 실행: python tests/test_flow_concurrency.py
2
+ export 는 끝나면 프로세스를 종료하므로 하위 프로세스로 돌린다."""
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ import tempfile
7
+
8
+ ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
9
+ sys.path.insert(0, ROOT)
10
+ from codeflowhub.flow import FlowDecorator # noqa: E402
11
+
12
+ WF = '''
13
+ import sys
14
+ sys.path.insert(0, {root!r})
15
+ from codeflowhub import flow, task
16
+
17
+ @task(image='busybox')
18
+ def t1(args):
19
+ return args
20
+
21
+ @flow(name='concurrency-check', repo='git@example.com:x/y.git', path='.', export_destination={out!r}{kw})
22
+ def wf(args):
23
+ t1(args)
24
+ '''
25
+
26
+
27
+ def _export(kw=''):
28
+ out = tempfile.mkdtemp()
29
+ src = os.path.join(out, 'wf.py')
30
+ with open(src, 'w') as fh:
31
+ fh.write(WF.format(root=ROOT, out=out, kw=kw))
32
+ subprocess.run([sys.executable, src, '--export', 'airflow'], check=True, capture_output=True)
33
+ with open(os.path.join(out, 'concurrency-check_dag.py')) as fh:
34
+ return fh.read()
35
+
36
+
37
+ def main():
38
+ for bad in (0, -1, '8', True, 1.5):
39
+ try:
40
+ FlowDecorator._positive_int('x', bad)
41
+ raise AssertionError(f'not rejected: {bad!r}')
42
+ except ValueError:
43
+ pass
44
+ assert FlowDecorator._positive_int('x', None) is None
45
+ assert FlowDecorator._positive_int('x', 32) == 32
46
+
47
+ dag = _export(', max_active_runs=32, max_active_tasks=8')
48
+ assert 'max_active_runs=32,' in dag and 'max_active_tasks=8,' in dag, dag
49
+ dag = _export()
50
+ assert 'max_active_runs' not in dag and 'max_active_tasks' not in dag, dag
51
+ print('test_flow_concurrency OK')
52
+
53
+
54
+ if __name__ == '__main__':
55
+ main()
File without changes
File without changes
File without changes