programgarden-community 2.1.0__tar.gz → 2.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 (114) hide show
  1. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/PKG-INFO +4 -5
  2. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/README.md +2 -3
  3. programgarden_community-2.3.0/programgarden_community/nodes/__init__.py +18 -0
  4. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/nodes/messaging/telegram.py +11 -0
  5. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/nodes_registry.py +0 -46
  6. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/pyproject.toml +2 -2
  7. programgarden_community-2.1.0/programgarden_community/nodes/__init__.py +0 -23
  8. programgarden_community-2.1.0/programgarden_community/nodes/data/__init__.py +0 -14
  9. programgarden_community-2.1.0/programgarden_community/nodes/data/_parsers.py +0 -343
  10. programgarden_community-2.1.0/programgarden_community/nodes/data/file_reader.py +0 -554
  11. programgarden_community-2.1.0/programgarden_community/nodes/market/__init__.py +0 -11
  12. programgarden_community-2.1.0/programgarden_community/nodes/market/fear_greed.py +0 -343
  13. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/__init__.py +0 -0
  14. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/nodes/analysis/__init__.py +0 -0
  15. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/nodes/analysis/performance_report.py +0 -0
  16. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/nodes/messaging/__init__.py +0 -0
  17. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/__init__.py +0 -0
  18. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/_position_qty.py +0 -0
  19. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/_ta_common.py +0 -0
  20. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/adx/__init__.py +0 -0
  21. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/aroon/__init__.py +0 -0
  22. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/atr/__init__.py +0 -0
  23. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/beta_hedge/__init__.py +0 -0
  24. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/bollinger_bands/README.md +0 -0
  25. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/bollinger_bands/__init__.py +0 -0
  26. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/breakout_retest/__init__.py +0 -0
  27. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/calendar_spread/__init__.py +0 -0
  28. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/calmar_ratio/__init__.py +0 -0
  29. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/cci/__init__.py +0 -0
  30. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/cmf/__init__.py +0 -0
  31. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/cmo/__init__.py +0 -0
  32. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/connors_rsi/__init__.py +0 -0
  33. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/contango_backwardation/__init__.py +0 -0
  34. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/coppock_curve/__init__.py +0 -0
  35. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/correlation_analysis/__init__.py +0 -0
  36. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/correlation_guard/__init__.py +0 -0
  37. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/dcf_fair_value/__init__.py +0 -0
  38. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/doji/__init__.py +0 -0
  39. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/drawdown_protection/__init__.py +0 -0
  40. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/dual_momentum/README.md +0 -0
  41. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/dual_momentum/__init__.py +0 -0
  42. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/dynamic_stop_loss/__init__.py +0 -0
  43. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/elder_ray/__init__.py +0 -0
  44. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/engulfing/__init__.py +0 -0
  45. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/ev_ebitda_screen/__init__.py +0 -0
  46. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/fisher_transform/__init__.py +0 -0
  47. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/golden_ratio/__init__.py +0 -0
  48. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/graham_number/__init__.py +0 -0
  49. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/hammer_shooting_star/__init__.py +0 -0
  50. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/heikin_ashi/__init__.py +0 -0
  51. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/hurst_exponent/__init__.py +0 -0
  52. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/ichimoku_cloud/__init__.py +0 -0
  53. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/kdj/__init__.py +0 -0
  54. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/kelly_criterion/__init__.py +0 -0
  55. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/keltner_channel/__init__.py +0 -0
  56. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/level_touch/__init__.py +0 -0
  57. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/ma_cross/README.md +0 -0
  58. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/ma_cross/__init__.py +0 -0
  59. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/macd/README.md +0 -0
  60. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/macd/__init__.py +0 -0
  61. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/magic_formula/__init__.py +0 -0
  62. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/market_internals/__init__.py +0 -0
  63. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/max_position_limit/__init__.py +0 -0
  64. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/mean_reversion/__init__.py +0 -0
  65. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/mfi/__init__.py +0 -0
  66. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/momentum_rank/__init__.py +0 -0
  67. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/morning_evening_star/__init__.py +0 -0
  68. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/multi_timeframe_confirmation/__init__.py +0 -0
  69. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/obv/__init__.py +0 -0
  70. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/pair_trading/__init__.py +0 -0
  71. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/parabolic_sar/__init__.py +0 -0
  72. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/partial_take_profit/__init__.py +0 -0
  73. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/piotroski_f_score/__init__.py +0 -0
  74. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/pivot_point/__init__.py +0 -0
  75. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/portfolio_optimizer/__init__.py +0 -0
  76. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/price_channel/__init__.py +0 -0
  77. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/profit_target/README.md +0 -0
  78. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/profit_target/__init__.py +0 -0
  79. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/qqe/__init__.py +0 -0
  80. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/regime_detection/__init__.py +0 -0
  81. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/relative_strength/__init__.py +0 -0
  82. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/risk_parity/__init__.py +0 -0
  83. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/roll_management/__init__.py +0 -0
  84. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/rsi/README.md +0 -0
  85. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/rsi/__init__.py +0 -0
  86. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/rsi_divergence/__init__.py +0 -0
  87. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/schaff_trend_cycle/__init__.py +0 -0
  88. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/seasonal_filter/__init__.py +0 -0
  89. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/sharpe_ratio_monitor/__init__.py +0 -0
  90. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/sortino_ratio/__init__.py +0 -0
  91. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/squeeze_momentum/__init__.py +0 -0
  92. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/stochastic/__init__.py +0 -0
  93. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/stop_loss/README.md +0 -0
  94. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/stop_loss/__init__.py +0 -0
  95. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/supertrend/__init__.py +0 -0
  96. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/support_resistance_levels/__init__.py +0 -0
  97. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/tactical_asset_allocation/__init__.py +0 -0
  98. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/three_line_strike/__init__.py +0 -0
  99. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/time_based_exit/__init__.py +0 -0
  100. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/time_series_momentum/__init__.py +0 -0
  101. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/trailing_stop/README.md +0 -0
  102. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/trailing_stop/__init__.py +0 -0
  103. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/trix/__init__.py +0 -0
  104. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/turtle_breakout/__init__.py +0 -0
  105. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/ultimate_oscillator/__init__.py +0 -0
  106. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/var_cvar_monitor/__init__.py +0 -0
  107. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/volatility_breakout/__init__.py +0 -0
  108. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/volatility_position_sizing/__init__.py +0 -0
  109. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/volume_spike/README.md +0 -0
  110. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/volume_spike/__init__.py +0 -0
  111. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/vortex_indicator/__init__.py +0 -0
  112. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/vwap/__init__.py +0 -0
  113. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/williams_r/__init__.py +0 -0
  114. {programgarden_community-2.1.0 → programgarden_community-2.3.0}/programgarden_community/plugins/z_score/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programgarden-community
3
- Version: 2.1.0
3
+ Version: 2.3.0
4
4
  Summary: ProgramGarden Community - 전략 플러그인 모음
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Author: 프로그램동산
@@ -18,7 +18,7 @@ Provides-Extra: portfolio
18
18
  Provides-Extra: quant-all
19
19
  Provides-Extra: xlsx
20
20
  Requires-Dist: packaging (>=23) ; extra == "portfolio" or extra == "quant-all"
21
- Requires-Dist: programgarden-core (>=2.1.0,<3.0.0)
21
+ Requires-Dist: programgarden-core (>=2.5.0,<3.0.0)
22
22
  Requires-Dist: pypdf (>=4.0.0)
23
23
  Requires-Dist: pyportfolioopt (>=1.6,<2) ; (python_version < "3.15") and (extra == "portfolio" or extra == "quant-all")
24
24
  Requires-Dist: quantstats (>=0.0.81,<0.1) ; extra == "perf" or extra == "quant-all"
@@ -119,13 +119,12 @@ poetry add programgarden-community
119
119
  | DynamicStopLoss | ATR 기반 동적 손절 |
120
120
  | LevelTouch | 레벨 터치/돌파/역할전환 감지 |
121
121
 
122
- ### 커뮤니티 노드 (4개)
122
+ ### 커뮤니티 노드 (2개)
123
123
 
124
124
  | 노드 | 카테고리 | 설명 |
125
125
  |------|----------|------|
126
126
  | TelegramNode | messaging | Telegram Bot API 메시지 전송 |
127
- | FearGreedIndexNode | market | CNN Fear & Greed Index 조회 |
128
- | FileReaderNode | data | 파일 파싱 (PDF, TXT, CSV, JSON, MD, DOCX, XLSX) |
127
+ | PerformanceReportNode | analysis | 성과/리스크 리포트 (Sharpe, Sortino, drawdown, CAGR, beta) |
129
128
 
130
129
  ## 사용법
131
130
 
@@ -92,13 +92,12 @@ poetry add programgarden-community
92
92
  | DynamicStopLoss | ATR 기반 동적 손절 |
93
93
  | LevelTouch | 레벨 터치/돌파/역할전환 감지 |
94
94
 
95
- ### 커뮤니티 노드 (4개)
95
+ ### 커뮤니티 노드 (2개)
96
96
 
97
97
  | 노드 | 카테고리 | 설명 |
98
98
  |------|----------|------|
99
99
  | TelegramNode | messaging | Telegram Bot API 메시지 전송 |
100
- | FearGreedIndexNode | market | CNN Fear & Greed Index 조회 |
101
- | FileReaderNode | data | 파일 파싱 (PDF, TXT, CSV, JSON, MD, DOCX, XLSX) |
100
+ | PerformanceReportNode | analysis | 성과/리스크 리포트 (Sharpe, Sortino, drawdown, CAGR, beta) |
102
101
 
103
102
  ## 사용법
104
103
 
@@ -0,0 +1,18 @@
1
+ """
2
+ ProgramGarden Community - 커뮤니티 노드
3
+
4
+ 커뮤니티에서 기여한 커스텀 노드 타입.
5
+ - messaging: TelegramNode 등 알림/메시징 노드
6
+ - analysis: PerformanceReportNode 등 성과 분석 노드
7
+
8
+ 사용 방법:
9
+ from programgarden_community.nodes import TelegramNode, PerformanceReportNode
10
+ """
11
+
12
+ from programgarden_community.nodes.messaging import TelegramNode
13
+ from programgarden_community.nodes.analysis import PerformanceReportNode
14
+
15
+ __all__ = [
16
+ "TelegramNode",
17
+ "PerformanceReportNode",
18
+ ]
@@ -46,6 +46,17 @@ class TelegramNode(BaseMessagingNode):
46
46
  """
47
47
 
48
48
  type: Literal["TelegramNode"] = "TelegramNode"
49
+
50
+ # 연결(자격증명) 선언 — 챗봇/편집기/검증기 공용. 텔레그램 봇 토큰은 발송에 필요하다.
51
+ _connection: ClassVar[Dict[str, Any]] = {
52
+ "purpose": "notify",
53
+ "need": "run",
54
+ "when": "always",
55
+ "label_key": "connection.TelegramNode.label",
56
+ "presets": [],
57
+ "missing": "draft",
58
+ }
59
+
49
60
  description: str = "Send Telegram messages using a registered bot token and receiving chat ID; key-free drafts can be saved before linking credentials."
50
61
 
51
62
  # 노드 아이콘 (텔레그램 로고)
@@ -46,40 +46,6 @@ def register_all_nodes() -> None:
46
46
  # 이미 등록된 경우 무시 (중복 호출 방지)
47
47
  pass
48
48
 
49
- # === Market Nodes ===
50
- from programgarden_community.nodes.market import FearGreedIndexNode
51
-
52
- market_nodes = [
53
- FearGreedIndexNode,
54
- ]
55
-
56
- for node_class in market_nodes:
57
- try:
58
- registry.register_community(
59
- node_class,
60
- source="community",
61
- trust_level="community",
62
- )
63
- except ValueError as e:
64
- pass
65
-
66
- # === Data Nodes ===
67
- from programgarden_community.nodes.data import FileReaderNode
68
-
69
- data_nodes = [
70
- FileReaderNode,
71
- ]
72
-
73
- for node_class in data_nodes:
74
- try:
75
- registry.register_community(
76
- node_class,
77
- source="community",
78
- trust_level="community",
79
- )
80
- except ValueError as e:
81
- pass
82
-
83
49
  # === Analysis Nodes ===
84
50
  from programgarden_community.nodes.analysis import PerformanceReportNode
85
51
 
@@ -109,18 +75,6 @@ def get_community_node_list() -> list:
109
75
  "description": "Send messages via Telegram Bot API",
110
76
  "requires_credential": True,
111
77
  },
112
- {
113
- "type": "FearGreedIndexNode",
114
- "category": "market",
115
- "description": "CNN Fear & Greed Index (0=Extreme Fear, 100=Extreme Greed)",
116
- "requires_credential": False,
117
- },
118
- {
119
- "type": "FileReaderNode",
120
- "category": "data",
121
- "description": "Read and parse files (PDF, TXT, CSV, JSON, MD)",
122
- "requires_credential": False,
123
- },
124
78
  {
125
79
  "type": "PerformanceReportNode",
126
80
  "category": "analysis",
@@ -5,7 +5,7 @@ authors = [
5
5
  homepage = "https://programgarden.com"
6
6
  requires-python = ">=3.12"
7
7
  name = "programgarden-community"
8
- version = "2.1.0"
8
+ version = "2.3.0"
9
9
  license = "AGPL-3.0-or-later"
10
10
  description = "ProgramGarden Community - 전략 플러그인 모음"
11
11
  readme = "README.md"
@@ -16,7 +16,7 @@ packages = [
16
16
  ]
17
17
 
18
18
  [tool.poetry.dependencies]
19
- programgarden-core = "^2.1.0"
19
+ programgarden-core = "^2.5.0"
20
20
  pypdf = ">=4.0.0"
21
21
  # Optional heavy quant deps (extras). 3.14 wheels validated (plan §1.3).
22
22
  # Only pyportfolioopt carries a python<3.15 marker to fence scikit-base (hard-capped
@@ -1,23 +0,0 @@
1
- """
2
- ProgramGarden Community - 커뮤니티 노드
3
-
4
- 커뮤니티에서 기여한 커스텀 노드 타입.
5
- - messaging: TelegramNode 등 알림/메시징 노드
6
- - market: FearGreedIndexNode 등 외부 시장 데이터 노드
7
- - data: FileReaderNode 등 데이터 처리 노드
8
-
9
- 사용 방법:
10
- from programgarden_community.nodes import TelegramNode, FearGreedIndexNode, FileReaderNode
11
- """
12
-
13
- from programgarden_community.nodes.messaging import TelegramNode
14
- from programgarden_community.nodes.market import FearGreedIndexNode
15
- from programgarden_community.nodes.data import FileReaderNode
16
- from programgarden_community.nodes.analysis import PerformanceReportNode
17
-
18
- __all__ = [
19
- "TelegramNode",
20
- "FearGreedIndexNode",
21
- "FileReaderNode",
22
- "PerformanceReportNode",
23
- ]
@@ -1,14 +0,0 @@
1
- """
2
- ProgramGarden Community - 데이터 노드
3
-
4
- 파일 읽기/파싱 등 데이터 처리 노드.
5
-
6
- 사용 방법:
7
- from programgarden_community.nodes.data import FileReaderNode
8
- """
9
-
10
- from programgarden_community.nodes.data.file_reader import FileReaderNode
11
-
12
- __all__ = [
13
- "FileReaderNode",
14
- ]
@@ -1,343 +0,0 @@
1
- """
2
- FileReaderNode 내부 파서 모듈
3
-
4
- 포맷별 파일 파싱 함수. 외부에서 직접 사용하지 않음.
5
- 각 파서는 (text, data, extra_metadata) 튜플을 반환합니다.
6
- """
7
-
8
- import csv
9
- import io
10
- import json
11
- from typing import Any, Dict, List, Optional, Tuple
12
-
13
-
14
- def parse_pdf(
15
- file_bytes: bytes,
16
- pages: Optional[str] = None,
17
- extract_tables: bool = False,
18
- ) -> Tuple[str, Any, Dict[str, Any]]:
19
- """
20
- PDF 파싱 (pypdf 사용)
21
-
22
- Args:
23
- file_bytes: PDF 파일 바이트
24
- pages: 페이지 범위 ("1-5", "1,3,5", None=전체)
25
- extract_tables: 테이블 추출 여부 (pdfplumber 필요)
26
-
27
- Returns:
28
- (text, data, extra_metadata)
29
- """
30
- try:
31
- from pypdf import PdfReader
32
- except ImportError:
33
- raise ImportError(
34
- "PDF 파싱에 pypdf가 필요합니다. "
35
- "설치: pip install pypdf"
36
- )
37
-
38
- reader = PdfReader(io.BytesIO(file_bytes))
39
- total_pages = len(reader.pages)
40
-
41
- # 페이지 범위 파싱
42
- page_indices = _parse_page_range(pages, total_pages)
43
-
44
- # 텍스트 추출
45
- texts = []
46
- for idx in page_indices:
47
- page = reader.pages[idx]
48
- text = page.extract_text() or ""
49
- texts.append(text)
50
-
51
- full_text = "\n".join(texts)
52
-
53
- # 테이블 추출 (pdfplumber)
54
- table_data = None
55
- if extract_tables:
56
- table_data = _extract_pdf_tables(file_bytes, page_indices)
57
-
58
- extra = {
59
- "page_count": total_pages,
60
- "extracted_pages": len(page_indices),
61
- }
62
-
63
- return full_text, table_data, extra
64
-
65
-
66
- def _parse_page_range(pages: Optional[str], total: int) -> List[int]:
67
- """
68
- 페이지 범위 문자열 → 0-based 인덱스 리스트
69
-
70
- Examples:
71
- "1-5" → [0,1,2,3,4]
72
- "1,3,5" → [0,2,4]
73
- "2-4,7" → [1,2,3,6]
74
- None → [0..total-1]
75
- """
76
- if not pages:
77
- return list(range(total))
78
-
79
- indices = set()
80
- for part in pages.split(","):
81
- part = part.strip()
82
- if "-" in part:
83
- start_str, end_str = part.split("-", 1)
84
- start = max(1, int(start_str.strip()))
85
- end = min(total, int(end_str.strip()))
86
- for i in range(start, end + 1):
87
- indices.add(i - 1) # 0-based
88
- else:
89
- page_num = int(part)
90
- if 1 <= page_num <= total:
91
- indices.add(page_num - 1)
92
-
93
- return sorted(indices)
94
-
95
-
96
- def _extract_pdf_tables(
97
- file_bytes: bytes, page_indices: List[int]
98
- ) -> Optional[List[List[List[str]]]]:
99
- """pdfplumber로 PDF 테이블 추출"""
100
- try:
101
- import pdfplumber
102
- except ImportError:
103
- raise ImportError(
104
- "PDF 테이블 추출에 pdfplumber가 필요합니다. "
105
- "설치: pip install pdfplumber"
106
- )
107
-
108
- tables = []
109
- with pdfplumber.open(io.BytesIO(file_bytes)) as pdf:
110
- for idx in page_indices:
111
- if idx < len(pdf.pages):
112
- page_tables = pdf.pages[idx].extract_tables()
113
- if page_tables:
114
- tables.extend(page_tables)
115
-
116
- return tables if tables else None
117
-
118
-
119
- def parse_txt(
120
- file_bytes: bytes,
121
- encoding: str = "utf-8",
122
- ) -> Tuple[str, Any, Dict[str, Any]]:
123
- """TXT/MD 파싱"""
124
- text = file_bytes.decode(encoding)
125
- line_count = text.count("\n") + (1 if text and not text.endswith("\n") else 0)
126
- return text, None, {"line_count": line_count}
127
-
128
-
129
- def parse_csv(
130
- file_bytes: bytes,
131
- encoding: str = "utf-8",
132
- delimiter: str = ",",
133
- has_header: bool = True,
134
- ) -> Tuple[str, Any, Dict[str, Any]]:
135
- """CSV 파싱 → 구조화 데이터 (list[dict]) + 원본 텍스트"""
136
- text = file_bytes.decode(encoding)
137
- reader = csv.reader(io.StringIO(text), delimiter=delimiter)
138
- rows = list(reader)
139
-
140
- if not rows:
141
- return text, [], {"row_count": 0, "column_count": 0}
142
-
143
- if has_header and len(rows) > 1:
144
- headers = rows[0]
145
- data = [dict(zip(headers, row)) for row in rows[1:]]
146
- row_count = len(data)
147
- elif has_header and len(rows) == 1:
148
- # 헤더만 있는 경우
149
- data = []
150
- row_count = 0
151
- else:
152
- data = rows
153
- row_count = len(rows)
154
-
155
- column_count = len(rows[0]) if rows else 0
156
-
157
- return text, data, {"row_count": row_count, "column_count": column_count}
158
-
159
-
160
- def parse_json(
161
- file_bytes: bytes,
162
- encoding: str = "utf-8",
163
- ) -> Tuple[str, Any, Dict[str, Any]]:
164
- """JSON 파싱 → 파싱된 객체 + 원본 텍스트"""
165
- text = file_bytes.decode(encoding)
166
- data = json.loads(text)
167
-
168
- extra: Dict[str, Any] = {}
169
- if isinstance(data, list):
170
- extra["item_count"] = len(data)
171
- elif isinstance(data, dict):
172
- extra["key_count"] = len(data)
173
-
174
- return text, data, extra
175
-
176
-
177
- def parse_docx(
178
- file_bytes: bytes,
179
- ) -> Tuple[str, Any, Dict[str, Any]]:
180
- """
181
- DOCX 파싱 (python-docx 사용)
182
-
183
- Returns:
184
- (text, data, extra_metadata)
185
- - text: 전체 문단 텍스트 (줄바꿈 연결)
186
- - data: 테이블 목록 (list[list[list[str]]]) 또는 None
187
- - extra_metadata: paragraph_count, table_count
188
- """
189
- try:
190
- from docx import Document
191
- except ImportError:
192
- raise ImportError(
193
- "DOCX 파싱에 python-docx가 필요합니다. "
194
- "설치: pip install python-docx"
195
- )
196
-
197
- doc = Document(io.BytesIO(file_bytes))
198
-
199
- # 문단 텍스트 추출
200
- paragraphs = [p.text for p in doc.paragraphs]
201
- full_text = "\n".join(paragraphs)
202
-
203
- # 테이블 추출
204
- tables = []
205
- for table in doc.tables:
206
- rows = []
207
- for row in table.rows:
208
- rows.append([cell.text for cell in row.cells])
209
- tables.append(rows)
210
-
211
- table_data = tables if tables else None
212
-
213
- extra = {
214
- "paragraph_count": len(paragraphs),
215
- "table_count": len(doc.tables),
216
- }
217
-
218
- return full_text, table_data, extra
219
-
220
-
221
- def parse_xlsx(
222
- file_bytes: bytes,
223
- sheet_name: Optional[str] = None,
224
- ) -> Tuple[str, Any, Dict[str, Any]]:
225
- """
226
- XLSX 파싱 (openpyxl 사용)
227
-
228
- Args:
229
- file_bytes: XLSX 파일 바이트
230
- sheet_name: 시트명 (None=활성 시트)
231
-
232
- Returns:
233
- (text, data, extra_metadata)
234
- - text: CSV 형식 텍스트
235
- - data: list[dict] (첫 행을 헤더로 사용) 또는 list[list]
236
- - extra_metadata: sheet_name, row_count, column_count, sheet_names
237
- """
238
- try:
239
- from openpyxl import load_workbook
240
- except ImportError:
241
- raise ImportError(
242
- "XLSX 파싱에 openpyxl이 필요합니다. "
243
- "설치: pip install openpyxl"
244
- )
245
-
246
- wb = load_workbook(io.BytesIO(file_bytes), read_only=True, data_only=True)
247
- sheet_names = wb.sheetnames
248
-
249
- if sheet_name and sheet_name in sheet_names:
250
- ws = wb[sheet_name]
251
- else:
252
- ws = wb.active
253
- sheet_name = ws.title
254
-
255
- # 모든 행 읽기
256
- rows = []
257
- for row in ws.iter_rows(values_only=True):
258
- rows.append([str(cell) if cell is not None else "" for cell in row])
259
-
260
- wb.close()
261
-
262
- if not rows:
263
- return "", [], {
264
- "sheet_name": sheet_name,
265
- "row_count": 0,
266
- "column_count": 0,
267
- "sheet_names": sheet_names,
268
- }
269
-
270
- # 첫 행을 헤더로 사용
271
- headers = rows[0]
272
- column_count = len(headers)
273
-
274
- if len(rows) > 1:
275
- data = [dict(zip(headers, row)) for row in rows[1:]]
276
- row_count = len(data)
277
- else:
278
- data = []
279
- row_count = 0
280
-
281
- # CSV 형식 텍스트 생성
282
- text_lines = [",".join(row) for row in rows]
283
- text = "\n".join(text_lines)
284
-
285
- extra = {
286
- "sheet_name": sheet_name,
287
- "row_count": row_count,
288
- "column_count": column_count,
289
- "sheet_names": sheet_names,
290
- }
291
-
292
- return text, data, extra
293
-
294
-
295
- # 포맷 → 확장자 매핑
296
- EXTENSION_MAP = {
297
- ".pdf": "pdf",
298
- ".txt": "txt",
299
- ".csv": "csv",
300
- ".json": "json",
301
- ".md": "md",
302
- ".docx": "docx",
303
- ".xlsx": "xlsx",
304
- }
305
-
306
- # PDF 매직 바이트
307
- _PDF_MAGIC = b"%PDF-"
308
-
309
-
310
- def detect_format(
311
- file_name: Optional[str] = None,
312
- file_path: Optional[str] = None,
313
- file_bytes: Optional[bytes] = None,
314
- ) -> str:
315
- """
316
- 파일 포맷 자동 감지
317
-
318
- 우선순위:
319
- 1. file_name 확장자
320
- 2. file_path 확장자
321
- 3. 매직 바이트 (PDF)
322
- 4. txt fallback
323
- """
324
- import os
325
-
326
- # 1. file_name 확장자
327
- if file_name:
328
- ext = os.path.splitext(file_name)[1].lower()
329
- if ext in EXTENSION_MAP:
330
- return EXTENSION_MAP[ext]
331
-
332
- # 2. file_path 확장자
333
- if file_path:
334
- ext = os.path.splitext(file_path)[1].lower()
335
- if ext in EXTENSION_MAP:
336
- return EXTENSION_MAP[ext]
337
-
338
- # 3. 매직 바이트
339
- if file_bytes and file_bytes[:5] == _PDF_MAGIC:
340
- return "pdf"
341
-
342
- # 4. fallback
343
- return "txt"