codeflowhub 0.2.3__tar.gz → 0.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.
Files changed (32) hide show
  1. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/PKG-INFO +1 -1
  2. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/flow.py +2 -2
  3. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/service/airflow_exporter.py +103 -1
  4. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/task.py +4 -1
  5. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub.egg-info/PKG-INFO +1 -1
  6. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/setup.py +1 -1
  7. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/LICENSE +0 -0
  8. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/README.md +0 -0
  9. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/__init__.py +0 -0
  10. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/action.py +0 -0
  11. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/airflow/__init__.py +0 -0
  12. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/airflow/xcom.py +0 -0
  13. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/base.py +0 -0
  14. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/model.py +0 -0
  15. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/service/__init__.py +0 -0
  16. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/storage/__init__.py +0 -0
  17. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/storage/local_storage.py +0 -0
  18. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/storage/s3_storage.py +0 -0
  19. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/storage/storage.py +0 -0
  20. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/template/__init__.py +0 -0
  21. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/template/analyze_speaker_pkg/__init__.py +0 -0
  22. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/template/analyze_speaker_pkg/main.py +0 -0
  23. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/template/extract_voice_pkg/__init__.py +0 -0
  24. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/template/extract_voice_pkg/main.py +0 -0
  25. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/template/read_pdf_pkg/__init__.py +0 -0
  26. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/template/read_pdf_pkg/main.py +0 -0
  27. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/template/transcript_pkg/__init__.py +0 -0
  28. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub/template/transcript_pkg/main.py +0 -0
  29. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub.egg-info/SOURCES.txt +0 -0
  30. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub.egg-info/dependency_links.txt +0 -0
  31. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/codeflowhub.egg-info/top_level.txt +0 -0
  32. {codeflowhub-0.2.3 → codeflowhub-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeflowhub
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: workflow development tools
5
5
  Author: creaddiscans
6
6
  Author-email: creaddiscans@gmail.com
@@ -353,8 +353,8 @@ class FlowDecorator(BaseDecorator):
353
353
  """개별 task 결과를 run.json에 저장"""
354
354
  run_log = self._load_log_file()
355
355
  run_log[task_name] = {
356
- 'input': result,
357
- 'output': result
356
+ 'input': self._strip_context(result),
357
+ 'output': self._strip_context(result)
358
358
  }
359
359
  self._write_log_file(run_log)
360
360
 
@@ -283,6 +283,7 @@ base_volume_mounts = [
283
283
 
284
284
  tolerations_code = self._build_tolerations_code(task)
285
285
  node_selector_code = self._build_node_selector_code(task)
286
+ affinity_code = self._build_affinity_code(task)
286
287
  volume_mounts_code = self._build_volume_mounts_code(task)
287
288
  container_resources_code = self._build_container_resources_code(task)
288
289
  sidecars_code = self._build_sidecars_code(task)
@@ -304,7 +305,7 @@ base_volume_mounts = [
304
305
  operator_code = f''' {task.name} = KubernetesPodOperator(
305
306
  **common,
306
307
  task_id='{task.name}',
307
- image='{task_image}',{pool_code}{trigger_rule_code}{retries_code}{env_vars_code}{tolerations_code}{node_selector_code}{volume_mounts_code}{container_resources_code}{sidecars_code}
308
+ image='{task_image}',{pool_code}{trigger_rule_code}{retries_code}{env_vars_code}{tolerations_code}{node_selector_code}{affinity_code}{volume_mounts_code}{container_resources_code}{sidecars_code}
308
309
  arguments=[
309
310
  f\'\'\'{arguments}\'\'\'
310
311
  ],
@@ -361,6 +362,107 @@ base_volume_mounts = [
361
362
  return f"\n node_selector={repr(task.node_selector)},"
362
363
  return ""
363
364
 
365
+ def _build_affinity_code(self, task):
366
+ """Affinity 코드 생성 (node_affinity 지원)
367
+
368
+ affinity dict는 K8s YAML 구조를 그대로 따르며, camelCase/snake_case 모두 허용.
369
+ 예:
370
+ affinity={
371
+ 'node_affinity': {
372
+ 'required_during_scheduling_ignored_during_execution': {
373
+ 'node_selector_terms': [
374
+ {'match_expressions': [
375
+ {'key': 'workload-type/spot', 'operator': 'In', 'values': ['true']}
376
+ ]}
377
+ ]
378
+ },
379
+ 'preferred_during_scheduling_ignored_during_execution': [
380
+ {'weight': 100, 'preference': {
381
+ 'match_expressions': [
382
+ {'key': 'workload-type/spot', 'operator': 'In', 'values': ['true']}
383
+ ]
384
+ }}
385
+ ]
386
+ }
387
+ }
388
+ """
389
+ if not (hasattr(task, 'affinity') and task.affinity):
390
+ return ""
391
+
392
+ aff = self._normalize_keys(task.affinity)
393
+ parts = []
394
+ if 'node_affinity' in aff:
395
+ node_aff_code = self._render_node_affinity(aff['node_affinity'])
396
+ if node_aff_code:
397
+ parts.append(f"node_affinity={node_aff_code}")
398
+
399
+ if not parts:
400
+ return ""
401
+
402
+ return f"\n affinity=k8s.V1Affinity({', '.join(parts)}),"
403
+
404
+ def _render_node_affinity(self, node_aff):
405
+ """k8s.V1NodeAffinity(...) 코드 생성"""
406
+ na = self._normalize_keys(node_aff)
407
+ parts = []
408
+
409
+ if 'required_during_scheduling_ignored_during_execution' in na:
410
+ required = self._normalize_keys(na['required_during_scheduling_ignored_during_execution'])
411
+ terms = required.get('node_selector_terms', [])
412
+ terms_code = ', '.join(self._render_node_selector_term(t) for t in terms)
413
+ parts.append(
414
+ f"required_during_scheduling_ignored_during_execution="
415
+ f"k8s.V1NodeSelector(node_selector_terms=[{terms_code}])"
416
+ )
417
+
418
+ if 'preferred_during_scheduling_ignored_during_execution' in na:
419
+ prefs = na['preferred_during_scheduling_ignored_during_execution'] or []
420
+ pref_codes = []
421
+ for pref in prefs:
422
+ p = self._normalize_keys(pref)
423
+ weight = p.get('weight', 1)
424
+ preference = self._render_node_selector_term(p.get('preference', {}))
425
+ pref_codes.append(
426
+ f"k8s.V1PreferredSchedulingTerm(weight={weight}, preference={preference})"
427
+ )
428
+ parts.append(
429
+ f"preferred_during_scheduling_ignored_during_execution=[{', '.join(pref_codes)}]"
430
+ )
431
+
432
+ return f"k8s.V1NodeAffinity({', '.join(parts)})"
433
+
434
+ def _render_node_selector_term(self, term):
435
+ """k8s.V1NodeSelectorTerm(...) 코드 생성"""
436
+ t = self._normalize_keys(term)
437
+ parts = []
438
+
439
+ for field_key, api_key in [('match_expressions', 'match_expressions'),
440
+ ('match_fields', 'match_fields')]:
441
+ if field_key in t:
442
+ req_codes = [self._render_node_selector_requirement(r) for r in t[field_key]]
443
+ parts.append(f"{api_key}=[{', '.join(req_codes)}]")
444
+
445
+ return f"k8s.V1NodeSelectorTerm({', '.join(parts)})"
446
+
447
+ def _render_node_selector_requirement(self, req):
448
+ """k8s.V1NodeSelectorRequirement(...) 코드 생성"""
449
+ r = self._normalize_keys(req)
450
+ req_parts = [f"key={repr(r['key'])}", f"operator={repr(r['operator'])}"]
451
+ if 'values' in r:
452
+ req_parts.append(f"values={repr(list(r['values']))}")
453
+ return f"k8s.V1NodeSelectorRequirement({', '.join(req_parts)})"
454
+
455
+ @staticmethod
456
+ def _normalize_keys(d):
457
+ """dict의 최상위 키를 camelCase → snake_case로 정규화"""
458
+ import re
459
+ if not isinstance(d, dict):
460
+ return d
461
+ return {
462
+ re.sub(r'([A-Z])', r'_\1', k).lower().lstrip('_'): v
463
+ for k, v in d.items()
464
+ }
465
+
364
466
  def _build_volume_mounts_code(self, task, include_base=True):
365
467
  """Volume mounts 코드 생성
366
468
 
@@ -13,11 +13,13 @@ class TaskDecorator(BaseDecorator):
13
13
  tolerations: list[Toleration]
14
14
  volume_mounts: list[VolumeMount]
15
15
  sidecars: list[SidecarContainer]
16
+ affinity: dict
16
17
 
17
18
  def __init__(self, *args, cpu='1', memory='1Gi', gpu=None, image=None,
18
19
  node_selector=None, tolerations: list[Toleration] = None,
19
20
  volume_mounts: list[VolumeMount] = None, pool: str = None,
20
- sidecars: list[SidecarContainer] = None, **kwargs):
21
+ sidecars: list[SidecarContainer] = None, affinity: dict = None,
22
+ **kwargs):
21
23
  super().__init__(*args, **kwargs)
22
24
  self.set_resource(cpu, memory, gpu)
23
25
  self.image = image
@@ -26,6 +28,7 @@ class TaskDecorator(BaseDecorator):
26
28
  self.volume_mounts = volume_mounts or []
27
29
  self.pool = pool
28
30
  self.sidecars = sidecars or []
31
+ self.affinity = affinity or {}
29
32
  self._is_flowhub_task = True
30
33
 
31
34
  def set_resource(self, cpu, memory, gpu):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeflowhub
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: workflow development tools
5
5
  Author: creaddiscans
6
6
  Author-email: creaddiscans@gmail.com
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='codeflowhub',
5
- version='0.2.3',
5
+ version='0.3.0',
6
6
  description='workflow development tools',
7
7
  author='creaddiscans',
8
8
  author_email='creaddiscans@gmail.com',
File without changes
File without changes
File without changes