codeflowhub 1.1.0__tar.gz → 1.3.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.
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/PKG-INFO +1 -1
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/flow.py +137 -8
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/service/airflow_exporter.py +57 -8
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub.egg-info/PKG-INFO +1 -1
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/setup.py +1 -1
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/LICENSE +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/README.md +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/__init__.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/action.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/airflow/__init__.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/airflow/xcom.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/base.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/model.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/service/__init__.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/storage/__init__.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/storage/local_storage.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/storage/s3_storage.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/storage/storage.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/task.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/template/__init__.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/template/analyze_speaker_pkg/__init__.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/template/analyze_speaker_pkg/main.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/template/extract_voice_pkg/__init__.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/template/extract_voice_pkg/main.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/template/read_pdf_pkg/__init__.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/template/read_pdf_pkg/main.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/template/transcript_pkg/__init__.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/template/transcript_pkg/main.py +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub.egg-info/SOURCES.txt +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub.egg-info/dependency_links.txt +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub.egg-info/top_level.txt +0 -0
- {codeflowhub-1.1.0 → codeflowhub-1.3.0}/setup.cfg +0 -0
|
@@ -3,6 +3,7 @@ import inspect
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
|
+
from datetime import datetime
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
from .base import BaseDecorator
|
|
@@ -28,13 +29,17 @@ class FlowDecorator(BaseDecorator):
|
|
|
28
29
|
path:str # Git repo 내 작업 경로
|
|
29
30
|
export_destination:str # Export 시 생성 파일 저장 디렉토리
|
|
30
31
|
default_args:dict # Airflow DAG default_args (owner, retries, retry_delay 등 override)
|
|
32
|
+
schedule:str # Airflow DAG 스케줄 (cron 문자열 / preset / timedelta / None=수동 트리거)
|
|
33
|
+
doc_md:str # Airflow DAG doc_md (UI에 표시될 Markdown 문서)
|
|
34
|
+
start_date:datetime # Airflow DAG start_date (None이면 export 시점)
|
|
31
35
|
secrets:list # 모든 task에 공통 주입할 K8s Secret(env) 목록
|
|
32
36
|
on_failure: 'BaseDecorator' = None # 모든 task의 기본 failure handler
|
|
33
37
|
|
|
34
38
|
def __init__(self, *args, namespace='default', env=None, name=None, description=None, params=None,
|
|
35
39
|
tags=None, annotations=None, service_account_name=None, volumes=None,
|
|
36
40
|
airflow_sidecar_image=None, airflow_connection_id=None, repo=None, path=None,
|
|
37
|
-
export_destination='dags', default_args=None, secrets=None, on_failure=None,
|
|
41
|
+
export_destination='dags', default_args=None, secrets=None, on_failure=None,
|
|
42
|
+
schedule=None, start_date=None, doc_md=None, schedule_interval=None, **kwargs):
|
|
38
43
|
# CLI 속성 먼저 초기화 (init()에서 사용됨)
|
|
39
44
|
self._cli_export = None
|
|
40
45
|
self._cli_job_dir = None
|
|
@@ -57,6 +62,11 @@ class FlowDecorator(BaseDecorator):
|
|
|
57
62
|
self.path = path
|
|
58
63
|
self.export_destination = export_destination or 'dags'
|
|
59
64
|
self.default_args = default_args or {}
|
|
65
|
+
self.schedule = self._normalize_schedule(
|
|
66
|
+
self._resolve_schedule_alias(schedule, schedule_interval)
|
|
67
|
+
)
|
|
68
|
+
self.start_date = start_date
|
|
69
|
+
self.doc_md = self._normalize_doc_md(doc_md)
|
|
60
70
|
# Design Ref: §3.2 — flow-level 공통 Secret(env)
|
|
61
71
|
self.secrets = secrets or []
|
|
62
72
|
self.on_failure = on_failure
|
|
@@ -67,6 +77,109 @@ class FlowDecorator(BaseDecorator):
|
|
|
67
77
|
# super().__init__()는 마지막에 호출 (init()이 실행됨)
|
|
68
78
|
super().__init__(*args, name=name, **kwargs)
|
|
69
79
|
|
|
80
|
+
@staticmethod
|
|
81
|
+
def _normalize_doc_md(doc_md):
|
|
82
|
+
"""doc_md 정규화 (선언 시점에 조기 실패)
|
|
83
|
+
|
|
84
|
+
- str: Markdown 본문. 함수 안에서 triple-quote로 쓴 경우의 공통 들여쓰기를 제거
|
|
85
|
+
- Path: 해당 파일의 내용을 읽어 사용 (README 등 별도 파일 참조용)
|
|
86
|
+
- None / 빈 문자열: doc_md 미설정
|
|
87
|
+
"""
|
|
88
|
+
import textwrap
|
|
89
|
+
|
|
90
|
+
if doc_md is None:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
if isinstance(doc_md, Path):
|
|
94
|
+
if not doc_md.exists():
|
|
95
|
+
raise FileNotFoundError(f'doc_md 파일을 찾을 수 없습니다: {doc_md}')
|
|
96
|
+
text = doc_md.read_text(encoding='utf-8')
|
|
97
|
+
elif isinstance(doc_md, str):
|
|
98
|
+
text = doc_md
|
|
99
|
+
else:
|
|
100
|
+
raise TypeError(
|
|
101
|
+
f'doc_md는 str(Markdown 본문), Path(파일 경로), None만 가능합니다. '
|
|
102
|
+
f'전달된 타입: {type(doc_md).__name__}'
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# 선행 개행 제거 후 dedent → triple-quote 블록의 들여쓰기가 코드블록으로 렌더되는 것 방지
|
|
106
|
+
text = textwrap.dedent(text.lstrip('\n')).rstrip()
|
|
107
|
+
return text or None
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def _resolve_schedule_alias(schedule, schedule_interval):
|
|
111
|
+
"""schedule / schedule_interval(구 이름) 중 하나로 정리
|
|
112
|
+
|
|
113
|
+
Airflow 2.4+에서 DAG 파라미터가 schedule로 통합되었고 Airflow 3에서
|
|
114
|
+
schedule_interval은 제거되었다. 구 이름도 계속 받되 경고한다.
|
|
115
|
+
"""
|
|
116
|
+
if schedule_interval is None:
|
|
117
|
+
return schedule
|
|
118
|
+
if schedule is not None:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
'schedule과 schedule_interval을 동시에 지정할 수 없습니다. '
|
|
121
|
+
'schedule만 사용하세요 (schedule_interval은 구 이름).'
|
|
122
|
+
)
|
|
123
|
+
print(
|
|
124
|
+
"⚠️ schedule_interval은 구 이름입니다. Airflow 2.4+ 표준인 "
|
|
125
|
+
"schedule을 사용하세요.",
|
|
126
|
+
file=sys.stderr
|
|
127
|
+
)
|
|
128
|
+
return schedule_interval
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def _normalize_schedule(schedule):
|
|
132
|
+
"""schedule 검증 (선언 시점에 조기 실패)
|
|
133
|
+
|
|
134
|
+
허용: None(수동 트리거), cron 문자열('0 2 * * *'), preset('@daily'), timedelta
|
|
135
|
+
"""
|
|
136
|
+
from datetime import timedelta
|
|
137
|
+
|
|
138
|
+
if schedule is None or isinstance(schedule, timedelta):
|
|
139
|
+
return schedule
|
|
140
|
+
|
|
141
|
+
if not isinstance(schedule, str):
|
|
142
|
+
raise TypeError(
|
|
143
|
+
f'schedule은 str(cron/preset), timedelta, None만 가능합니다. '
|
|
144
|
+
f'전달된 타입: {type(schedule).__name__}'
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
value = schedule.strip()
|
|
148
|
+
if not value:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
presets = {'@once', '@hourly', '@daily', '@weekly', '@monthly',
|
|
152
|
+
'@quarterly', '@yearly', '@annually', '@continuous', '@none'}
|
|
153
|
+
if value.startswith('@'):
|
|
154
|
+
if value not in presets:
|
|
155
|
+
raise ValueError(
|
|
156
|
+
f'알 수 없는 스케줄 preset: "{value}". '
|
|
157
|
+
f'사용 가능: {", ".join(sorted(presets))}'
|
|
158
|
+
)
|
|
159
|
+
return None if value == '@none' else value
|
|
160
|
+
|
|
161
|
+
# cron 표현식은 5개 필드 (분 시 일 월 요일)
|
|
162
|
+
fields = value.split()
|
|
163
|
+
if len(fields) != 5:
|
|
164
|
+
raise ValueError(
|
|
165
|
+
f'cron 표현식은 5개 필드(분 시 일 월 요일)여야 합니다. '
|
|
166
|
+
f'전달된 값: "{value}" ({len(fields)}개 필드)'
|
|
167
|
+
)
|
|
168
|
+
return value
|
|
169
|
+
|
|
170
|
+
def _resolve_start_date(self):
|
|
171
|
+
"""DAG start_date 결정 (Airflow는 스케줄이 있으면 start_date 필수)
|
|
172
|
+
|
|
173
|
+
우선순위: start_date 파라미터 > default_args['start_date'] > 고정 기본값.
|
|
174
|
+
기본값을 datetime.now()가 아닌 고정값으로 두어 재export 시 결정적이도록 한다.
|
|
175
|
+
"""
|
|
176
|
+
if self.start_date is not None:
|
|
177
|
+
return self.start_date
|
|
178
|
+
from_default_args = (self.default_args or {}).get('start_date')
|
|
179
|
+
if from_default_args is not None:
|
|
180
|
+
return from_default_args
|
|
181
|
+
return datetime(2024, 1, 1)
|
|
182
|
+
|
|
70
183
|
def _initialize_env(self, env):
|
|
71
184
|
"""환경 설정 초기화"""
|
|
72
185
|
if env and isinstance(env, dict) and all(isinstance(v, dict) for v in env.values()):
|
|
@@ -258,28 +371,33 @@ class FlowDecorator(BaseDecorator):
|
|
|
258
371
|
print(f' Supported: airflow')
|
|
259
372
|
os._exit(1)
|
|
260
373
|
|
|
261
|
-
from datetime import
|
|
374
|
+
from datetime import timedelta
|
|
262
375
|
|
|
263
376
|
flow_id = self.flow_name or self.name
|
|
264
377
|
dest_dir = self.export_destination or 'dags'
|
|
265
378
|
print(f'🚀 Exporting {flow_id} to Airflow DAG... (destination: {dest_dir})')
|
|
266
379
|
|
|
380
|
+
# 스케줄이 설정된 DAG는 start_date가 필수이므로 default_args에 항상 포함시킨다
|
|
381
|
+
start_date = self._resolve_start_date()
|
|
267
382
|
default_args = {
|
|
268
383
|
'owner': 'flowhub',
|
|
269
384
|
'retries': 1,
|
|
270
385
|
'retry_delay': timedelta(minutes=5),
|
|
271
386
|
**(self.default_args or {}),
|
|
387
|
+
'start_date': start_date,
|
|
272
388
|
}
|
|
273
389
|
|
|
274
390
|
dag_path = self.export_airflow(
|
|
275
391
|
output_path=os.path.join(dest_dir, f'{flow_id}_dag.py'),
|
|
276
|
-
|
|
277
|
-
start_date=
|
|
392
|
+
schedule=self.schedule,
|
|
393
|
+
start_date=start_date,
|
|
278
394
|
default_args=default_args,
|
|
279
395
|
)
|
|
280
396
|
|
|
281
397
|
print(f'✅ Successfully exported to: {dag_path}')
|
|
282
398
|
print(f'📋 DAG ID: {flow_id}')
|
|
399
|
+
print(f'⏰ Schedule: {self.schedule or "None (manual trigger)"}')
|
|
400
|
+
print(f'📅 Start date: {start_date.isoformat()}')
|
|
283
401
|
print(f'🏷️ Tags: [{self.namespace}]')
|
|
284
402
|
os._exit(0)
|
|
285
403
|
|
|
@@ -576,23 +694,34 @@ class FlowDecorator(BaseDecorator):
|
|
|
576
694
|
f'--input-data를 제공하거나 먼저 전체 workflow를 실행하세요.'
|
|
577
695
|
)
|
|
578
696
|
|
|
579
|
-
def export_airflow(self, output_path=None,
|
|
697
|
+
def export_airflow(self, output_path=None, schedule=None, start_date=None, default_args=None,
|
|
698
|
+
schedule_interval=None):
|
|
580
699
|
"""
|
|
581
700
|
Airflow DAG로 export
|
|
582
701
|
|
|
583
702
|
Args:
|
|
584
703
|
output_path: DAG 파일 저장 경로 (기본: dags/{dag_id}.py)
|
|
585
|
-
|
|
586
|
-
start_date: DAG 시작
|
|
704
|
+
schedule: DAG 스케줄. 미지정 시 @flow(schedule=...) 값 사용
|
|
705
|
+
start_date: DAG 시작 날짜. 미지정 시 @flow(start_date=...) 또는 기본값 사용
|
|
587
706
|
default_args: DAG default_args
|
|
707
|
+
schedule_interval: schedule의 구 이름 (deprecated)
|
|
588
708
|
|
|
589
709
|
Returns:
|
|
590
710
|
str: 생성된 DAG 파일 경로
|
|
591
711
|
"""
|
|
712
|
+
# 인자 미지정 시 flow 데코레이터에 선언된 값으로 폴백
|
|
713
|
+
schedule = self._resolve_schedule_alias(schedule, schedule_interval)
|
|
714
|
+
if schedule is None:
|
|
715
|
+
schedule = self.schedule
|
|
716
|
+
else:
|
|
717
|
+
schedule = self._normalize_schedule(schedule)
|
|
718
|
+
if start_date is None:
|
|
719
|
+
start_date = self._resolve_start_date()
|
|
720
|
+
|
|
592
721
|
exporter = AirflowExporter(self)
|
|
593
722
|
return exporter.export(
|
|
594
723
|
output_path=output_path,
|
|
595
|
-
|
|
724
|
+
schedule=schedule,
|
|
596
725
|
start_date=start_date,
|
|
597
726
|
default_args=default_args
|
|
598
727
|
)
|
|
@@ -149,7 +149,7 @@ class AirflowExporter:
|
|
|
149
149
|
|
|
150
150
|
def export(self,
|
|
151
151
|
output_path: str = None,
|
|
152
|
-
|
|
152
|
+
schedule: Optional[str] = None,
|
|
153
153
|
start_date: Optional[datetime] = None,
|
|
154
154
|
default_args: Optional[dict] = None):
|
|
155
155
|
"""Airflow DAG Python 파일로 export"""
|
|
@@ -178,7 +178,7 @@ class AirflowExporter:
|
|
|
178
178
|
configmap_path = self._generate_configmap_yaml(dir_path)
|
|
179
179
|
|
|
180
180
|
# DAG 코드 생성
|
|
181
|
-
dag_code = self._generate_dag_code(
|
|
181
|
+
dag_code = self._generate_dag_code(schedule, start_date, default_args)
|
|
182
182
|
|
|
183
183
|
# 파일 저장
|
|
184
184
|
with open(output_path, 'w', encoding='utf-8') as f:
|
|
@@ -192,7 +192,7 @@ class AirflowExporter:
|
|
|
192
192
|
print(f'📋 Apply ConfigMap: kubectl apply -f {configmap_path}')
|
|
193
193
|
return output_path
|
|
194
194
|
|
|
195
|
-
def _generate_dag_code(self,
|
|
195
|
+
def _generate_dag_code(self, schedule, start_date, default_args):
|
|
196
196
|
"""DAG Python 코드 생성 (target.py 스타일)"""
|
|
197
197
|
|
|
198
198
|
flow_name = self.flow.flow_name or self.flow.name
|
|
@@ -269,13 +269,24 @@ SETUP_SCRIPT = """{setup_script}"""
|
|
|
269
269
|
params_str = self._format_dict(self.flow.params, indent=1)
|
|
270
270
|
params_line = f"\n params={params_str},"
|
|
271
271
|
|
|
272
|
-
|
|
272
|
+
# doc_md 설정 — 본문은 DAG_DOC_MD 변수로 분리 (여러 줄 Markdown)
|
|
273
|
+
doc_md_definition = ""
|
|
274
|
+
doc_md_line = ""
|
|
275
|
+
doc_md = getattr(self.flow, 'doc_md', None)
|
|
276
|
+
if doc_md:
|
|
277
|
+
doc_md_definition = f'''# DAG documentation (Airflow UI)
|
|
278
|
+
DAG_DOC_MD = {self._render_doc_md(doc_md)}
|
|
279
|
+
|
|
280
|
+
'''
|
|
281
|
+
doc_md_line = "\n doc_md=DAG_DOC_MD,"
|
|
282
|
+
|
|
283
|
+
dag_definition = f'''{doc_md_definition}dag = DAG(
|
|
273
284
|
'{self.dag_id}',
|
|
274
285
|
default_args={default_args_str},
|
|
275
286
|
description='{description}',
|
|
276
|
-
|
|
287
|
+
schedule={self._render_schedule(schedule)},
|
|
277
288
|
catchup=False,
|
|
278
|
-
tags={tags_str},{params_line}
|
|
289
|
+
tags={tags_str},{params_line}{doc_md_line}
|
|
279
290
|
)
|
|
280
291
|
|
|
281
292
|
'''
|
|
@@ -881,6 +892,32 @@ base_volume_mounts = [
|
|
|
881
892
|
dependencies.append(f"[{deps_str}] >> {task.name}")
|
|
882
893
|
return dependencies
|
|
883
894
|
|
|
895
|
+
def _render_doc_md(self, doc_md):
|
|
896
|
+
"""Markdown 본문 → 생성 DAG의 문자열 리터럴 코드
|
|
897
|
+
|
|
898
|
+
줄 단위로 repr()을 적용해 이어붙인다. triple-quote를 쓰면 본문에 포함된
|
|
899
|
+
따옴표/백슬래시가 문법 오류를 만들 수 있으므로 repr로 완전히 이스케이프하고,
|
|
900
|
+
줄마다 나누어 생성 DAG에서도 읽을 수 있게 유지한다.
|
|
901
|
+
"""
|
|
902
|
+
lines = doc_md.split('\n')
|
|
903
|
+
if len(lines) == 1:
|
|
904
|
+
return repr(lines[0])
|
|
905
|
+
|
|
906
|
+
rendered = [f" {line + chr(10)!r}" for line in lines[:-1]]
|
|
907
|
+
rendered.append(f" {lines[-1]!r}")
|
|
908
|
+
return '(\n' + '\n'.join(rendered) + '\n)'
|
|
909
|
+
|
|
910
|
+
def _render_schedule(self, schedule):
|
|
911
|
+
"""schedule → DAG 코드 문자열
|
|
912
|
+
|
|
913
|
+
repr(timedelta)는 'datetime.timedelta(...)'를 만들지만 생성 DAG는
|
|
914
|
+
`from datetime import timedelta`만 import하므로 NameError가 된다.
|
|
915
|
+
"""
|
|
916
|
+
if isinstance(schedule, timedelta):
|
|
917
|
+
total = int(schedule.total_seconds())
|
|
918
|
+
return f'timedelta(seconds={total})'
|
|
919
|
+
return repr(schedule)
|
|
920
|
+
|
|
884
921
|
def _format_dict(self, d, indent=0, base_indent=0):
|
|
885
922
|
"""Dict를 Python 코드 형식으로 포맷팅"""
|
|
886
923
|
return self._format_dict_with_raw_values(d, indent, raw_keys=set(), base_indent=base_indent)
|
|
@@ -908,9 +945,21 @@ base_volume_mounts = [
|
|
|
908
945
|
elif isinstance(value, str):
|
|
909
946
|
items.append(f"'{key}': '{value}'")
|
|
910
947
|
elif isinstance(value, datetime):
|
|
911
|
-
|
|
948
|
+
# 시/분/초가 있으면 함께 렌더 (start_date 정밀도 손실 방지)
|
|
949
|
+
if (value.hour, value.minute, value.second) == (0, 0, 0):
|
|
950
|
+
items.append(f"'{key}': datetime({value.year}, {value.month}, {value.day})")
|
|
951
|
+
else:
|
|
952
|
+
items.append(
|
|
953
|
+
f"'{key}': datetime({value.year}, {value.month}, {value.day}, "
|
|
954
|
+
f"{value.hour}, {value.minute}, {value.second})"
|
|
955
|
+
)
|
|
912
956
|
elif isinstance(value, timedelta):
|
|
913
|
-
|
|
957
|
+
# 분 단위로 나누어떨어지지 않는 값은 초 단위로 렌더 (절삭 방지)
|
|
958
|
+
total = int(value.total_seconds())
|
|
959
|
+
if total % 60 == 0:
|
|
960
|
+
items.append(f"'{key}': timedelta(minutes={total // 60})")
|
|
961
|
+
else:
|
|
962
|
+
items.append(f"'{key}': timedelta(seconds={total})")
|
|
914
963
|
elif isinstance(value, bool):
|
|
915
964
|
items.append(f"'{key}': {value}")
|
|
916
965
|
elif isinstance(value, (int, float)):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeflowhub-1.1.0 → codeflowhub-1.3.0}/codeflowhub/template/analyze_speaker_pkg/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|