programgarden 1.2.1__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.
Files changed (31) hide show
  1. {programgarden-1.2.1 → programgarden-1.3.0}/PKG-INFO +2 -2
  2. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/executor.py +60 -16
  3. {programgarden-1.2.1 → programgarden-1.3.0}/pyproject.toml +2 -2
  4. {programgarden-1.2.1 → programgarden-1.3.0}/README.md +0 -0
  5. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/__init__.py +0 -0
  6. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/binding_validator.py +0 -0
  7. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/client.py +0 -0
  8. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/context.py +0 -0
  9. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/database/__init__.py +0 -0
  10. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/database/query_builder.py +0 -0
  11. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/database/workflow_position_tracker.py +0 -0
  12. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/database/workflow_risk_tracker.py +0 -0
  13. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/plugin/__init__.py +0 -0
  14. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/plugin/sandbox.py +0 -0
  15. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/providers/__init__.py +0 -0
  16. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/providers/llm_errors.py +0 -0
  17. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/providers/llm_provider.py +0 -0
  18. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/reconnect_handler.py +0 -0
  19. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/resolver.py +0 -0
  20. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/resource/__init__.py +0 -0
  21. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/resource/context.py +0 -0
  22. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/resource/limiter.py +0 -0
  23. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/resource/monitor.py +0 -0
  24. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/resource/throttle.py +0 -0
  25. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/__init__.py +0 -0
  26. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/credential_tools.py +0 -0
  27. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/definition_tools.py +0 -0
  28. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/event_tools.py +0 -0
  29. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/job_tools.py +0 -0
  30. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/registry_tools.py +0 -0
  31. {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/sqlite_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: programgarden
3
- Version: 1.2.1
3
+ Version: 1.3.0
4
4
  Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
5
5
  Author: 프로그램동산
6
6
  Author-email: coding@programgarden.com
@@ -13,7 +13,7 @@ Requires-Dist: aiosqlite (>=0.20.0,<0.21.0)
13
13
  Requires-Dist: croniter (>=6.0.0,<7.0.0)
14
14
  Requires-Dist: litellm (>=1.40.0)
15
15
  Requires-Dist: lxml (>=6.0.2,<7.0.0)
16
- Requires-Dist: programgarden-community (>=1.2.0,<2.0.0)
16
+ Requires-Dist: programgarden-community (>=1.3.0,<2.0.0)
17
17
  Requires-Dist: programgarden-core (>=1.2.0,<2.0.0)
18
18
  Requires-Dist: programgarden-finance (>=1.2.0,<2.0.0)
19
19
  Requires-Dist: psutil (>=6.0.0,<7.0.0)
@@ -5445,16 +5445,17 @@ class ConditionNodeExecutor(NodeExecutorBase):
5445
5445
  context.log("warning", f"External binding evaluation failed for {target_field}: {e}", node_id)
5446
5446
  external_values[target_field] = source_expr
5447
5447
 
5448
+ # 기본 컨텍스트 dict (row 제외)
5449
+ base_dict = expr_context.to_dict()
5450
+
5448
5451
  # 각 row 처리
5449
5452
  for row in from_data:
5450
5453
  record = {}
5451
5454
 
5452
- # row 컨텍스트 추가
5453
- row_context = {
5454
- **expr_context,
5455
- "row": row,
5456
- }
5457
- row_evaluator = ExpressionEvaluator(row_context)
5455
+ # row 컨텍스트 추가: 기본 dict에 row 추가
5456
+ row_ctx = ExpressionContext()
5457
+ row_ctx.variables = {**base_dict, "row": row}
5458
+ row_evaluator = ExpressionEvaluator(row_ctx)
5458
5459
 
5459
5460
  for target_field, source_expr in extract.items():
5460
5461
  if target_field in external_values:
@@ -5578,11 +5579,12 @@ class ConditionNodeExecutor(NodeExecutorBase):
5578
5579
  # === 표현식 평가 (items 제외 - items는 _process_items_with_extract에서 처리) ===
5579
5580
  context.log("debug", f"ConditionNode '{node_id}' 실행: plugin={plugin_id}, is_positions_based={is_positions_based}", node_id)
5580
5581
 
5581
- # items를 제외한 나머지 필드만 평가 (items.from, items.extract는 별도 처리)
5582
- items_backup = config.get("items")
5583
- config = evaluate_all_bindings(config, context, node_id)
5584
- if items_backup:
5585
- config["items"] = items_backup # items는 원본 유지
5582
+ # items를 제외한 나머지 필드만 평가 (items는 _process_items_with_extract에서 별도 처리)
5583
+ items_orig = config.get("items")
5584
+ config_without_items = {k: v for k, v in config.items() if k != "items"}
5585
+ config = evaluate_all_bindings(config_without_items, context, node_id)
5586
+ if items_orig is not None:
5587
+ config["items"] = items_orig
5586
5588
 
5587
5589
  # === positions 기반 플러그인 처리 (ProfitTarget, StopLoss) ===
5588
5590
  if is_positions_based:
@@ -5827,9 +5829,17 @@ class ConditionNodeExecutor(NodeExecutorBase):
5827
5829
  "fields": fields, # 플러그인 파라미터
5828
5830
  "field_mapping": field_mapping, # 필드 매핑
5829
5831
  "symbols": normalized_symbols, # [{exchange, symbol}, ...]
5830
- "context": context, # ExecutionContext (risk_tracker 등)
5831
5832
  }
5832
5833
 
5834
+ # context는 시그니처에 있는 플러그인에만 전달 (trailing_stop 등)
5835
+ import inspect
5836
+ try:
5837
+ sig = inspect.signature(plugin)
5838
+ if "context" in sig.parameters:
5839
+ plugin_kwargs["context"] = context
5840
+ except (ValueError, TypeError):
5841
+ pass
5842
+
5833
5843
  # 추가 포트 데이터
5834
5844
  if held_symbols:
5835
5845
  plugin_kwargs["held_symbols"] = held_symbols
@@ -12165,6 +12175,19 @@ class WorkflowJob:
12165
12175
  if edge.to_node_id == node_id:
12166
12176
  # symbols 포트 우선 확인 (WatchlistNode 출력)
12167
12177
  input_data = self.context.get_output(edge.from_node_id, "symbols")
12178
+
12179
+ # symbols가 문자열 배열이면 (merge 후) value 포트로 폴백
12180
+ # - WatchlistNode symbols: [{exchange, symbol}, ...] → dict 배열 → 그대로 사용
12181
+ # - HistoricalDataNode merged symbols: ["TSLA"] → string 배열 → value 포트로 전환
12182
+ if (isinstance(input_data, list) and input_data
12183
+ and not isinstance(input_data[0], dict)):
12184
+ value_data = self.context.get_output(edge.from_node_id, "value")
12185
+ if value_data is not None:
12186
+ if isinstance(value_data, list):
12187
+ input_data = value_data
12188
+ elif isinstance(value_data, dict):
12189
+ input_data = [value_data]
12190
+
12168
12191
  if input_data is None:
12169
12192
  # 기본 출력 확인
12170
12193
  input_data = self.context.get_output(edge.from_node_id, None)
@@ -12814,17 +12837,38 @@ class WorkflowJob:
12814
12837
  - {{ input.xxx }}: 워크플로우 inputs 파라미터
12815
12838
  - {{ nodeId.port }}: 이전 노드 출력값
12816
12839
  - {{ context.xxx }}: 실행 컨텍스트 값
12840
+
12841
+ Note: items 키는 제외 (ConditionNode의 _process_items_with_extract에서 별도 처리).
12842
+ items 내부에 {{ row.xxx }} 같은 지연 평가 표현식이 있어 여기서 평가하면 실패함.
12817
12843
  """
12818
12844
  from programgarden_core.expression import ExpressionEvaluator
12819
-
12845
+
12846
+ # items는 별도 처리 대상이므로 항상 제외
12847
+ items_orig = config.get("items")
12848
+ config_copy = {k: v for k, v in config.items() if k != "items"}
12849
+
12850
+ # iteration context 없으면 {{ item }} 표현식도 지연 (auto-iterate 전)
12851
+ deferred = {}
12852
+ if self.context._iteration_item is None:
12853
+ for k in list(config_copy.keys()):
12854
+ v = config_copy[k]
12855
+ if isinstance(v, str) and "{{ item" in v:
12856
+ deferred[k] = config_copy.pop(k)
12857
+
12820
12858
  try:
12821
12859
  expr_context = self.context.get_expression_context()
12822
12860
  evaluator = ExpressionEvaluator(expr_context)
12823
- return evaluator.evaluate_fields(config)
12861
+ resolved = evaluator.evaluate_fields(config_copy)
12824
12862
  except Exception as e:
12825
- # 표현식 평가 실패 시 원본 반환 (graceful degradation)
12826
12863
  self.context.log("warning", f"Expression resolve failed: {e}")
12827
- return config
12864
+ resolved = config_copy
12865
+
12866
+ # 지연 평가 필드 복원
12867
+ resolved.update(deferred)
12868
+ if items_orig is not None:
12869
+ resolved["items"] = items_orig
12870
+
12871
+ return resolved
12828
12872
 
12829
12873
  async def _event_loop(self) -> None:
12830
12874
  """
@@ -5,7 +5,7 @@ authors = [
5
5
  homepage = "https://programgarden.com"
6
6
  requires-python = ">=3.12"
7
7
  name = "programgarden"
8
- version = "1.2.1"
8
+ version = "1.3.0"
9
9
  description = "ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진"
10
10
  readme = "README.md"
11
11
 
@@ -30,7 +30,7 @@ aiosqlite = "^0.20.0"
30
30
  litellm = ">=1.40.0"
31
31
  programgarden-core = "^1.2.0"
32
32
  programgarden-finance = "^1.2.0"
33
- programgarden-community = "^1.2.0"
33
+ programgarden-community = "^1.3.0"
34
34
 
35
35
  [tool.poetry.group.dev.dependencies]
36
36
  pytest = "^8.0.0"
File without changes