programgarden-core 2.6.3__tar.gz → 3.0.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.
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/PKG-INFO +2 -52
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/README.md +1 -51
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/bases/listener.py +1 -1
- programgarden_core-3.0.0/programgarden_core/code_node.py +29 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/i18n/locales/en.json +4 -2
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/i18n/locales/ko.json +4 -2
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/backtest_stock.py +56 -8
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/pyproject.toml +1 -1
- programgarden_core-2.6.3/programgarden_core/code_node.py +0 -419
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/__init__.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/bases/__init__.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/bases/client.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/bases/components.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/bases/mixins.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/bases/products.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/bases/sql.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/bases/storage.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/exceptions.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/expression/__init__.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/expression/evaluator.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/expression/reference.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/i18n/__init__.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/i18n/translator.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/korea_alias.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/market_calendar.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/__init__.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/connection_rule.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/credential.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/edge.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/event.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/exchange.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/field_binding.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/job.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/order_diagnostics.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/plugin_resource.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/resilience.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/resource.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/validation.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/workflow.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/__init__.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/account_futures.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/account_korea_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/account_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/ai.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/backtest.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/backtest_futures.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/backtest_korea_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/base.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/broker.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/calculation.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/code.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/condition.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/contract_info_futures.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/data.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/data_futures.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/data_korea_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/data_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/display.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/event.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/fundamental_korea_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/fundamental_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/infra.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/open_orders_futures.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/open_orders_korea_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/open_orders_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/order.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/orderable_quantity_futures.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/portfolio.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/ranking_korea_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/ranking_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/realtime_futures.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/realtime_korea_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/realtime_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/risk.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/session_gate.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol_futures.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol_korea_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol_stock.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/trigger.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/presets/__init__.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/presets/news_analyst.json +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/presets/risk_manager.json +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/presets/strategist.json +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/presets/technical_analyst.json +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/registry/__init__.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/registry/credential_registry.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/registry/node_registry.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/registry/plugin_registry.py +0 -0
- {programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/retry_executor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: programgarden-core
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의
|
|
5
5
|
License-Expression: AGPL-3.0-or-later
|
|
6
6
|
Author: 프로그램동산
|
|
@@ -15,24 +15,8 @@ Requires-Dist: pydantic (>=2.0.1,<3.0.0)
|
|
|
15
15
|
Requires-Dist: tzdata (>=2025.2,<2026.0)
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
18
|
-
The 2.6.2 release candidate [futures minute alias correction](../../docs/futures-minute-alias-contract.md) aligns canonical minute periods with the existing one-page o3103 path and rejects unsupported hourly/adjustment claims. Complete-range coverage and consumer adoption are not implied.
|
|
19
|
-
|
|
20
|
-
US regular-session requests can opt into SessionGateNode.exchange_calendar (XNYS/XNAS) for scheduled holidays and early closes. See [session gates](../../docs/session-gates-and-guarded-split.md).
|
|
21
|
-
|
|
22
18
|
# ProgramGarden Core
|
|
23
19
|
|
|
24
|
-
An unreleased [stock history interval correction](../../docs/stock-history-interval-contract.md)
|
|
25
|
-
limits the overseas-stock catalog to its implemented g3204 periods and rejects
|
|
26
|
-
unsupported intraday requests before execution. Package adoption is pending.
|
|
27
|
-
|
|
28
|
-
Unreleased schema additions: OverseasFuturesContractInfoNode, SQLiteNode's opt-in
|
|
29
|
-
storage_scope=execution, and SessionGateNode's XHKG calendar. See the
|
|
30
|
-
[runtime contract](../../docs/futures-contract-details-and-rollover-state.md).
|
|
31
|
-
|
|
32
|
-
The REST overseas-stock position catalog declares `sellable_qty`, matching the
|
|
33
|
-
broker-reported capacity preserved by the engine. Missing evidence remains null;
|
|
34
|
-
see [the contract](../../docs/overseas-stock-sellable-quantity.md).
|
|
35
|
-
|
|
36
20
|
ProgramGarden은 AI 시대에 맞춰 파이썬을 모르는 투자자도 개인화된 시스템 트레이딩을 자동으로 수행할 수 있게 돕는 오픈소스입니다. 본 저장소는 노드 기반 DSL의 핵심 타입, 베이스 클래스, 레지스트리, i18n을 정의하는 "코어" 모듈입니다.
|
|
37
21
|
|
|
38
22
|
- 문서(비개발자 빠른 시작): https://programgarden.gitbook.io/docs/invest/non_dev_quick_guide
|
|
@@ -119,44 +103,10 @@ programgarden_core/
|
|
|
119
103
|
|
|
120
104
|
자세한 변경 사항은 `CHANGELOG.md`를 참고하세요.
|
|
121
105
|
|
|
122
|
-
## Workflow PnL event contract
|
|
123
|
-
|
|
124
|
-
Futures workflow PnL events preserve native gross estimates separately from
|
|
125
|
-
accounting results. `workflow_*`, `other_*`, `total_*`, account and competition
|
|
126
|
-
monetary/rate scalars remain null when accounting evidence is unavailable.
|
|
127
|
-
`pnl_by_currency` contains gross price-change subtotals by contract currency;
|
|
128
|
-
`monetary_positions` retains their basis/status and unmodified, unconfirmed
|
|
129
|
-
`broker_pnl_amount`. No fee, FX, margin/equity return or verified contest score
|
|
130
|
-
is inferred from these estimates. Actual zero and negative amounts are retained.
|
|
131
|
-
`currency` is null for mixed or unavailable currencies. Consumers must handle
|
|
132
|
-
nullable monetary fields and must not coerce them to zero.
|
|
133
|
-
|
|
134
|
-
`WorkflowPnLEvent.personal_metrics` is an optional versioned envelope for retained
|
|
135
|
-
local workflow executions. Version 1 includes `scope` (kind, product, provider,
|
|
136
|
-
trading mode), timezone-aware `as_of`, `basis`, executed-order count/status,
|
|
137
|
-
per-symbol/exchange/currency realized amounts/status, and unavailable MDD.
|
|
138
|
-
It is unverified personal evidence, independent of whole-account contest results.
|
|
139
|
-
Stock realized amounts are stored long-only FIFO gross values excluding fees;
|
|
140
|
-
unknown currency stays null. Futures monetary values and portfolio MDD require
|
|
141
|
-
accounting evidence absent from this ledger and remain null. Consumers must
|
|
142
|
-
preserve null/status and use the latest cumulative observation, not sum dates.
|
|
143
|
-
|
|
144
106
|
## Workflow account boundary
|
|
145
107
|
|
|
146
108
|
Each workflow allows one account and one product through at most one broker
|
|
147
109
|
connection. Additional connections produce `DUPLICATE_BROKER_NODE`, including
|
|
148
110
|
unbound or cross-product nodes. Reuse one broker for all consumers. Account-free
|
|
149
|
-
workflows and unrelated credentials remain supported.
|
|
150
|
-
[the shared contract](../../docs/workflow_account_policy.md).
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
## Cancellation result contract (unreleased P4)
|
|
154
|
-
|
|
155
|
-
CancelOrder metadata describes request acknowledgement (`accepted`,
|
|
156
|
-
`confirmation_pending=true`), not final cancellation. Replacement orders require
|
|
157
|
-
separate matching completion evidence. See
|
|
158
|
-
[the cancellation contract](../../docs/cancellation-acknowledgements.md).
|
|
159
|
-
|
|
160
|
-
Session windows and IfNode routing inside Split are described in
|
|
161
|
-
[Session gates and guarded Split](../../docs/session-gates-and-guarded-split.md).
|
|
111
|
+
workflows and unrelated credentials remain supported.
|
|
162
112
|
|
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
The 2.6.2 release candidate [futures minute alias correction](../../docs/futures-minute-alias-contract.md) aligns canonical minute periods with the existing one-page o3103 path and rejects unsupported hourly/adjustment claims. Complete-range coverage and consumer adoption are not implied.
|
|
2
|
-
|
|
3
|
-
US regular-session requests can opt into SessionGateNode.exchange_calendar (XNYS/XNAS) for scheduled holidays and early closes. See [session gates](../../docs/session-gates-and-guarded-split.md).
|
|
4
|
-
|
|
5
1
|
# ProgramGarden Core
|
|
6
2
|
|
|
7
|
-
An unreleased [stock history interval correction](../../docs/stock-history-interval-contract.md)
|
|
8
|
-
limits the overseas-stock catalog to its implemented g3204 periods and rejects
|
|
9
|
-
unsupported intraday requests before execution. Package adoption is pending.
|
|
10
|
-
|
|
11
|
-
Unreleased schema additions: OverseasFuturesContractInfoNode, SQLiteNode's opt-in
|
|
12
|
-
storage_scope=execution, and SessionGateNode's XHKG calendar. See the
|
|
13
|
-
[runtime contract](../../docs/futures-contract-details-and-rollover-state.md).
|
|
14
|
-
|
|
15
|
-
The REST overseas-stock position catalog declares `sellable_qty`, matching the
|
|
16
|
-
broker-reported capacity preserved by the engine. Missing evidence remains null;
|
|
17
|
-
see [the contract](../../docs/overseas-stock-sellable-quantity.md).
|
|
18
|
-
|
|
19
3
|
ProgramGarden은 AI 시대에 맞춰 파이썬을 모르는 투자자도 개인화된 시스템 트레이딩을 자동으로 수행할 수 있게 돕는 오픈소스입니다. 본 저장소는 노드 기반 DSL의 핵심 타입, 베이스 클래스, 레지스트리, i18n을 정의하는 "코어" 모듈입니다.
|
|
20
4
|
|
|
21
5
|
- 문서(비개발자 빠른 시작): https://programgarden.gitbook.io/docs/invest/non_dev_quick_guide
|
|
@@ -102,43 +86,9 @@ programgarden_core/
|
|
|
102
86
|
|
|
103
87
|
자세한 변경 사항은 `CHANGELOG.md`를 참고하세요.
|
|
104
88
|
|
|
105
|
-
## Workflow PnL event contract
|
|
106
|
-
|
|
107
|
-
Futures workflow PnL events preserve native gross estimates separately from
|
|
108
|
-
accounting results. `workflow_*`, `other_*`, `total_*`, account and competition
|
|
109
|
-
monetary/rate scalars remain null when accounting evidence is unavailable.
|
|
110
|
-
`pnl_by_currency` contains gross price-change subtotals by contract currency;
|
|
111
|
-
`monetary_positions` retains their basis/status and unmodified, unconfirmed
|
|
112
|
-
`broker_pnl_amount`. No fee, FX, margin/equity return or verified contest score
|
|
113
|
-
is inferred from these estimates. Actual zero and negative amounts are retained.
|
|
114
|
-
`currency` is null for mixed or unavailable currencies. Consumers must handle
|
|
115
|
-
nullable monetary fields and must not coerce them to zero.
|
|
116
|
-
|
|
117
|
-
`WorkflowPnLEvent.personal_metrics` is an optional versioned envelope for retained
|
|
118
|
-
local workflow executions. Version 1 includes `scope` (kind, product, provider,
|
|
119
|
-
trading mode), timezone-aware `as_of`, `basis`, executed-order count/status,
|
|
120
|
-
per-symbol/exchange/currency realized amounts/status, and unavailable MDD.
|
|
121
|
-
It is unverified personal evidence, independent of whole-account contest results.
|
|
122
|
-
Stock realized amounts are stored long-only FIFO gross values excluding fees;
|
|
123
|
-
unknown currency stays null. Futures monetary values and portfolio MDD require
|
|
124
|
-
accounting evidence absent from this ledger and remain null. Consumers must
|
|
125
|
-
preserve null/status and use the latest cumulative observation, not sum dates.
|
|
126
|
-
|
|
127
89
|
## Workflow account boundary
|
|
128
90
|
|
|
129
91
|
Each workflow allows one account and one product through at most one broker
|
|
130
92
|
connection. Additional connections produce `DUPLICATE_BROKER_NODE`, including
|
|
131
93
|
unbound or cross-product nodes. Reuse one broker for all consumers. Account-free
|
|
132
|
-
workflows and unrelated credentials remain supported.
|
|
133
|
-
[the shared contract](../../docs/workflow_account_policy.md).
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
## Cancellation result contract (unreleased P4)
|
|
137
|
-
|
|
138
|
-
CancelOrder metadata describes request acknowledgement (`accepted`,
|
|
139
|
-
`confirmation_pending=true`), not final cancellation. Replacement orders require
|
|
140
|
-
separate matching completion evidence. See
|
|
141
|
-
[the cancellation contract](../../docs/cancellation-acknowledgements.md).
|
|
142
|
-
|
|
143
|
-
Session windows and IfNode routing inside Split are described in
|
|
144
|
-
[Session gates and guarded Split](../../docs/session-gates-and-guarded-split.md).
|
|
94
|
+
workflows and unrelated credentials remain supported.
|
|
@@ -346,7 +346,7 @@ class WorkflowPnLEvent:
|
|
|
346
346
|
# 워크플로우 **비율**을 못 낸 사유. 값을 비우는 것만으로는 "안 샀다" 와 "증권사가
|
|
347
347
|
# 평단·현재가를 안 보냈다" 가 구분되지 않는다 — 서버가 이 사유를 그대로 적재한다.
|
|
348
348
|
# 어휘: no_workflow_positions | basis_unreported | price_unreported | no_tracker |
|
|
349
|
-
# computation_failed (
|
|
349
|
+
# computation_failed (플랫폼의 수익률 미산출 사유 어휘와 공용).
|
|
350
350
|
# 🔴 monetary_* 를 재사용하지 않는다 — 그건 선물 전용 계약이고, 주식 이벤트에서
|
|
351
351
|
# monetary_status is None 임을 고정하는 테스트가 있다.
|
|
352
352
|
workflow_rate_unavailable_reason: Optional[str] = None
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Public stub of the CodeNode support module. The user-code screener and sandbox are
|
|
2
|
+
not part of the open-source build; CodeNode is declared so workflows parse and pass
|
|
3
|
+
structural validation, but execution refuses (see WorkflowExecutor)."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CodeScreenResult:
|
|
7
|
+
"""Structural-validation result. The open-source build performs no code screening."""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.ok = True
|
|
11
|
+
self.error_code = None
|
|
12
|
+
self.message = None
|
|
13
|
+
self.suggestion = None
|
|
14
|
+
self.details = {}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def compile_code_node(code="", node_id="code", *args, **kwargs):
|
|
18
|
+
return CodeScreenResult()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
DEFAULT_ALLOWED_IMPORTS: frozenset = frozenset({
|
|
22
|
+
"math", "cmath", "statistics", "decimal", "fractions", "numbers",
|
|
23
|
+
"random", "json", "re", "datetime", "time", "calendar",
|
|
24
|
+
"collections", "itertools", "functools",
|
|
25
|
+
"string", "textwrap", "unicodedata",
|
|
26
|
+
"heapq", "bisect", "array", "copy",
|
|
27
|
+
"dataclasses", "enum", "typing",
|
|
28
|
+
"hashlib", "hmac", "base64", "binascii", "zlib",
|
|
29
|
+
})
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/i18n/locales/en.json
RENAMED
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
"nodes.OverseasStockBrokerNode.name": "Stock Broker",
|
|
145
145
|
"nodes.OverseasStockCancelOrderNode.description": "Cancels a pending US stock order by its original order ID. Requires original_order_id from OverseasStockOpenOrdersNode or OverseasStockRealOrderEventNode. Outputs cancel result and cancelled order ID. Rate-limited to 5-second minimum interval. Typical connection: StockOpenOrdersNode → StockCancelOrderNode, or StockRealOrderEventNode → StockCancelOrderNode.",
|
|
146
146
|
"nodes.OverseasStockCancelOrderNode.name": "Stock Cancel Order",
|
|
147
|
-
"nodes.OverseasStockHistoricalDataNode.description": "Fetches historical OHLCV (Open, High, Low, Close, Volume) chart data for overseas stocks over a specified date range. Uses LS g3204 for daily, weekly, monthly and yearly bars
|
|
147
|
+
"nodes.OverseasStockHistoricalDataNode.description": "Fetches historical OHLCV (Open, High, Low, Close, Volume) chart data for overseas stocks over a specified date range. Uses LS g3204 for daily, weekly, monthly and yearly bars and LS g3203 for 1/5/10/15/30/60-minute bars (one page of the newest up to 500 bars). Enable 'adjust' for split/dividend-adjusted prices (recommended for backtesting). Auto-iterates when previous node outputs multiple symbols. Requires an upstream OverseasStockBrokerNode. Typical connection: WatchlistNode → StockHistoricalDataNode → ConditionNode or → BacktestEngineNode. Supported exchanges: NYSE, NASDAQ, AMEX. Can be used as an AI Agent tool.",
|
|
148
148
|
"nodes.OverseasStockHistoricalDataNode.name": "Stock Historical Data",
|
|
149
149
|
"nodes.OverseasStockFundamentalNode.description": "Fetches fundamental financial data for overseas stocks via LS Securities API. Provides 16 metrics including PER, EPS, PBR, market capitalization, dividend yield, 52-week high/low, and more. Auto-iterates when previous node outputs multiple symbols. Requires an upstream OverseasStockBrokerNode. Typical connection: WatchlistNode → StockFundamentalNode → ConditionNode (for value-based screening). Supported exchanges: NYSE, NASDAQ, AMEX. Can be used as an AI Agent tool.",
|
|
150
150
|
"nodes.OverseasStockFundamentalNode.name": "Stock Fundamentals",
|
|
@@ -430,7 +430,7 @@
|
|
|
430
430
|
"fields.OverseasStockHistoricalDataNode.adjust.short": "Apply adjusted price",
|
|
431
431
|
"fields.OverseasStockHistoricalDataNode.end_date": "End date (YYYYMMDD)",
|
|
432
432
|
"fields.OverseasStockHistoricalDataNode.end_date.help_text": "e.g., 20241231. With binding: {{ date.today(format='yyyymmdd') }}",
|
|
433
|
-
"fields.OverseasStockHistoricalDataNode.interval": "
|
|
433
|
+
"fields.OverseasStockHistoricalDataNode.interval": "Bar size: 1d, 1w, 1M, 1Y (LS g3204) or 1m, 5m, 10m, 15m, 30m, 60m minute bars (LS g3203, newest up to 500 bars)",
|
|
434
434
|
"fields.OverseasStockHistoricalDataNode.start_date": "Start date (YYYYMMDD)",
|
|
435
435
|
"fields.OverseasStockHistoricalDataNode.start_date.help_text": "e.g., 20240101. With binding: {{ date.ago(30, format='yyyymmdd') }}",
|
|
436
436
|
"fields.OverseasStockHistoricalDataNode.symbol": "Enter the symbol code to query",
|
|
@@ -1059,6 +1059,7 @@
|
|
|
1059
1059
|
"enums.kr_price_type.conditional_limit": "Conditional Limit",
|
|
1060
1060
|
"enums.kr_price_type.limit": "Limit",
|
|
1061
1061
|
"enums.kr_price_type.market": "Market",
|
|
1062
|
+
"enums.interval.10m": "10 Minutes",
|
|
1062
1063
|
"enums.interval.15m": "15 Minutes",
|
|
1063
1064
|
"enums.interval.1M": "1 Month",
|
|
1064
1065
|
"enums.interval.1Y": "1 year",
|
|
@@ -1069,6 +1070,7 @@
|
|
|
1069
1070
|
"enums.interval.1m": "1 Minute",
|
|
1070
1071
|
"enums.interval.1w": "1 Week",
|
|
1071
1072
|
"enums.interval.5m": "5 Minutes",
|
|
1073
|
+
"enums.interval.60m": "60 Minutes",
|
|
1072
1074
|
"enums.operator.all": "All Match (AND) - Every condition must pass",
|
|
1073
1075
|
"enums.operator.any": "Any Match (OR) - At least one must pass",
|
|
1074
1076
|
"enums.operator.at_least": "At Least N - threshold or more must pass",
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/i18n/locales/ko.json
RENAMED
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
"nodes.OverseasStockBrokerNode.name": "해외주식 브로커",
|
|
145
145
|
"nodes.OverseasStockCancelOrderNode.description": "미국 주식 미체결 주문을 원래 주문 ID로 취소합니다. OverseasStockOpenOrdersNode 또는 OverseasStockRealOrderEventNode에서 original_order_id를 받아야 합니다. 출력: 취소 결과, 취소된 주문 ID. 최소 5초 간격 제한 적용. 일반적인 연결: StockOpenOrdersNode → StockCancelOrderNode 또는 StockRealOrderEventNode → StockCancelOrderNode.",
|
|
146
146
|
"nodes.OverseasStockCancelOrderNode.name": "해외주식 취소주문",
|
|
147
|
-
"nodes.OverseasStockHistoricalDataNode.description": "지정 기간의 해외주식 과거 OHLCV(시가, 고가, 저가, 종가, 거래량) 차트 데이터를 조회합니다. LS g3204로
|
|
147
|
+
"nodes.OverseasStockHistoricalDataNode.description": "지정 기간의 해외주식 과거 OHLCV(시가, 고가, 저가, 종가, 거래량) 차트 데이터를 조회합니다. LS g3204로 일봉·주봉·월봉·연봉을, LS g3203으로 1·5·10·15·30·60분봉(최근 최대 500개 한 페이지)을 조회합니다. 수정주가(주식 분할/배당 반영) 활성화 권장(백테스트용). 이전 노드가 여러 종목을 출력하면 자동 반복 실행. 상위 OverseasStockBrokerNode 연결 필수. 일반적인 연결: WatchlistNode → StockHistoricalDataNode → ConditionNode 또는 → BacktestEngineNode. 지원 거래소: NYSE, NASDAQ, AMEX. AI Agent 도구로 사용 가능.",
|
|
148
148
|
"nodes.OverseasStockHistoricalDataNode.name": "해외주식 과거데이터",
|
|
149
149
|
"nodes.OverseasStockFundamentalNode.description": "LS증권 API로 해외주식 펀더멘털 재무 데이터를 조회합니다. PER, EPS, PBR, 시가총액, 배당수익률, 52주 고/저가 등 16개 지표를 제공합니다. 이전 노드가 여러 종목을 출력하면 자동 반복 실행. 상위 OverseasStockBrokerNode 연결 필수. 일반적인 연결: WatchlistNode → StockFundamentalNode → ConditionNode (가치 기반 스크리닝). 지원 거래소: NYSE, NASDAQ, AMEX. AI Agent 도구로 사용 가능.",
|
|
150
150
|
"nodes.OverseasStockFundamentalNode.name": "해외주식 종목정보",
|
|
@@ -430,7 +430,7 @@
|
|
|
430
430
|
"fields.OverseasStockHistoricalDataNode.adjust.short": "수정주가 적용 여부",
|
|
431
431
|
"fields.OverseasStockHistoricalDataNode.end_date": "조회 종료일 (YYYYMMDD)",
|
|
432
432
|
"fields.OverseasStockHistoricalDataNode.end_date.help_text": "예: 20241231. 바인딩 시 {{ date.today(format='yyyymmdd') }} 사용 가능",
|
|
433
|
-
"fields.OverseasStockHistoricalDataNode.interval": "
|
|
433
|
+
"fields.OverseasStockHistoricalDataNode.interval": "봉 종류: 1d, 1w, 1M, 1Y (LS g3204) 또는 1m, 5m, 10m, 15m, 30m, 60m 분봉 (LS g3203, 최근 최대 500개)",
|
|
434
434
|
"fields.OverseasStockHistoricalDataNode.start_date": "조회 시작일 (YYYYMMDD)",
|
|
435
435
|
"fields.OverseasStockHistoricalDataNode.start_date.help_text": "예: 20240101. 바인딩 시 {{ date.ago(30, format='yyyymmdd') }} 사용 가능",
|
|
436
436
|
"fields.OverseasStockHistoricalDataNode.symbol": "조회할 종목 코드를 입력하세요",
|
|
@@ -1059,6 +1059,7 @@
|
|
|
1059
1059
|
"enums.kr_price_type.conditional_limit": "조건부지정가 (Conditional Limit)",
|
|
1060
1060
|
"enums.kr_price_type.limit": "지정가 (Limit)",
|
|
1061
1061
|
"enums.kr_price_type.market": "시장가 (Market)",
|
|
1062
|
+
"enums.interval.10m": "10분",
|
|
1062
1063
|
"enums.interval.15m": "15분",
|
|
1063
1064
|
"enums.interval.1M": "1개월",
|
|
1064
1065
|
"enums.interval.1Y": "1년",
|
|
@@ -1069,6 +1070,7 @@
|
|
|
1069
1070
|
"enums.interval.1m": "1분",
|
|
1070
1071
|
"enums.interval.1w": "1주",
|
|
1071
1072
|
"enums.interval.5m": "5분",
|
|
1073
|
+
"enums.interval.60m": "60분",
|
|
1072
1074
|
"enums.operator.all": "전체 만족 (AND) - 모든 조건 통과 필요",
|
|
1073
1075
|
"enums.operator.any": "하나 이상 (OR) - 1개만 통과해도 OK",
|
|
1074
1076
|
"enums.operator.at_least": "최소 N개 이상 - threshold 개수 이상 통과",
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/backtest_stock.py
RENAMED
|
@@ -65,9 +65,10 @@ class OverseasStockHistoricalDataNode(BaseNode):
|
|
|
65
65
|
default="{{ date.today(format='yyyymmdd') }}",
|
|
66
66
|
description="End date (YYYY-MM-DD or {{ date.today(format='yyyymmdd') }})",
|
|
67
67
|
)
|
|
68
|
-
interval: Literal["1d", "1w", "1M", "1Y"] = Field(
|
|
68
|
+
interval: Literal["1d", "1w", "1M", "1Y", "1m", "5m", "10m", "15m", "30m", "60m"] = Field(
|
|
69
69
|
default="1d",
|
|
70
|
-
description="LS g3204
|
|
70
|
+
description=("Bar size: 1d, 1w, 1M, 1Y read LS g3204; 1m, 5m, 10m, 15m, 30m, 60m read LS g3203 "
|
|
71
|
+
"minute bars (one page, newest up to 500 bars)."),
|
|
71
72
|
)
|
|
72
73
|
adjust: bool = Field(
|
|
73
74
|
default=False,
|
|
@@ -81,6 +82,7 @@ class OverseasStockHistoricalDataNode(BaseNode):
|
|
|
81
82
|
_usage: ClassVar[Dict[str, Any]] = {
|
|
82
83
|
"when_to_use": [
|
|
83
84
|
"Retrieve daily, weekly, monthly or yearly OHLCV history for a US-listed stock to compute technical indicators or run backtests",
|
|
85
|
+
"Retrieve 1/5/10/15/30/60-minute bars of a US-listed stock for an intraday indicator (one page of the newest bars)",
|
|
84
86
|
"Feed time-series data into ConditionNode plugins (RSI, MACD, Bollinger, etc.) that require a `data` list of candles",
|
|
85
87
|
"Use `{{ date.ago(N, format='yyyymmdd') }}` for start_date to get a rolling lookback window without hardcoding dates",
|
|
86
88
|
],
|
|
@@ -97,7 +99,9 @@ class OverseasStockHistoricalDataNode(BaseNode):
|
|
|
97
99
|
}
|
|
98
100
|
_features: ClassVar[List[str]] = [
|
|
99
101
|
"Returns a list of OHLCV candles in `value.time_series`: [{date, open, high, low, close, volume}, ...]",
|
|
100
|
-
"Supports intervals: 1d, 1w, 1M, 1Y through LS g3204
|
|
102
|
+
"Supports intervals: 1d, 1w, 1M, 1Y through LS g3204 and 1m, 5m, 10m, 15m, 30m, 60m through LS g3203",
|
|
103
|
+
"A minute interval reads ONE g3203 page: the newest up to 500 bars at or before end_date within start_date..end_date; a longer range is not completed by paging",
|
|
104
|
+
"A minute bar's date is YYYYMMDDHHMMSS in the exchange's local time as LS labels it; pre-market, after-hours and overnight-session bars are included as LS returns them",
|
|
101
105
|
"start_date / end_date accept YYYYMMDD strings or expression namespace calls: `{{ date.ago(30, format='yyyymmdd') }}`",
|
|
102
106
|
"adjust=True applies split/dividend-adjusted prices for accurate long-term backtesting",
|
|
103
107
|
"is_tool_enabled=True — AI Agent can fetch historical data autonomously for technical analysis",
|
|
@@ -156,12 +160,48 @@ class OverseasStockHistoricalDataNode(BaseNode):
|
|
|
156
160
|
},
|
|
157
161
|
"expected_output": "value port: {symbol, exchange, time_series: [{date, open, high, low, close, volume}, ...]}. RSI is computed from time_series.",
|
|
158
162
|
},
|
|
163
|
+
{
|
|
164
|
+
"title": "15-minute bars for an intraday RSI",
|
|
165
|
+
"description": "Fetch the newest 15-minute bars of NVDA (LS g3203, one page of up to 500 bars) and compute RSI on them. Bar dates are YYYYMMDDHHMMSS in exchange local time and include extended-hours bars.",
|
|
166
|
+
"workflow_snippet": {
|
|
167
|
+
"id": "overseas_stock_minute_rsi",
|
|
168
|
+
"name": "Minute-bar RSI Signal",
|
|
169
|
+
"nodes": [
|
|
170
|
+
{"id": "start", "type": "StartNode"},
|
|
171
|
+
{"id": "broker", "type": "OverseasStockBrokerNode", "credential_id": "broker_cred", "paper_trading": False},
|
|
172
|
+
{"id": "split", "type": "SplitNode", "array": [{"symbol": "NVDA", "exchange": "NASDAQ"}]},
|
|
173
|
+
{"id": "historical", "type": "OverseasStockHistoricalDataNode", "symbol": "{{ nodes.split.item }}", "start_date": "{{ date.ago(5, format='yyyymmdd') }}", "end_date": "{{ date.today(format='yyyymmdd') }}", "interval": "15m"},
|
|
174
|
+
{"id": "condition", "type": "ConditionNode", "plugin": "RSI", "items": {"from": "{{ item.time_series }}", "extract": {"symbol": "{{ item.symbol }}", "exchange": "{{ item.exchange }}", "date": "{{ row.date }}", "close": "{{ row.close }}"}}, "fields": {"period": 14, "threshold": 30, "direction": "below"}},
|
|
175
|
+
{"id": "split_results", "type": "AggregateNode", "mode": "collect"},
|
|
176
|
+
],
|
|
177
|
+
"edges": [
|
|
178
|
+
{"from": "start", "to": "broker"},
|
|
179
|
+
{"from": "broker", "to": "split"},
|
|
180
|
+
{"from": "split", "to": "historical"},
|
|
181
|
+
{"from": "broker", "to": "historical"},
|
|
182
|
+
{"from": "historical", "to": "condition"},
|
|
183
|
+
{"from": "condition", "to": "split_results"},
|
|
184
|
+
],
|
|
185
|
+
"credentials": [
|
|
186
|
+
{
|
|
187
|
+
"credential_id": "broker_cred",
|
|
188
|
+
"type": "broker_ls_overseas_stock",
|
|
189
|
+
"data": [
|
|
190
|
+
{"key": "appkey", "value": "", "type": "password", "label": "App Key"},
|
|
191
|
+
{"key": "appsecret", "value": "", "type": "password", "label": "App Secret"},
|
|
192
|
+
],
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
"expected_output": "value port: {symbol, exchange, time_series: [{date: 'YYYYMMDDHHMMSS', open, high, low, close, volume}, ...]} oldest first. RSI is computed from the 15-minute closes.",
|
|
197
|
+
},
|
|
159
198
|
]
|
|
160
199
|
_node_guide: ClassVar[Dict[str, Any]] = {
|
|
161
200
|
"input_handling": (
|
|
162
201
|
"The `symbol` field takes a single {exchange, symbol} dict — bind to `{{ nodes.split.item }}` in a per-symbol loop. "
|
|
163
202
|
"start_date / end_date accept 'YYYYMMDD' strings or `{{ date.ago(N, format='yyyymmdd') }}` expressions. "
|
|
164
|
-
"Supported intervals: 1d, 1w, 1M, 1Y through LS g3204
|
|
203
|
+
"Supported intervals: 1d, 1w, 1M, 1Y through LS g3204; 1m, 5m, 10m, 15m, 30m, 60m through LS g3203 "
|
|
204
|
+
"(one page of the newest up to 500 bars, date = YYYYMMDDHHMMSS exchange local time)."
|
|
165
205
|
),
|
|
166
206
|
"output_consumption": (
|
|
167
207
|
"The `value` port emits a dict: {symbol, exchange, time_series: [{date, open, high, low, close, volume}, ...]}. "
|
|
@@ -177,9 +217,11 @@ class OverseasStockHistoricalDataNode(BaseNode):
|
|
|
177
217
|
"pitfalls": [
|
|
178
218
|
"`symbol: {{ item }}` / `{{ nodes.<split>.item }}` resolves only inside a per-symbol loop (auto-iterate over an upstream array, or a SplitNode branch). If the upstream array is empty for this run the node logs `No symbols to fetch` and returns an empty series — normal no-signal, not a defect. A `No symbols provided` WARNING means the symbol source is missing or the binding did not resolve",
|
|
179
219
|
"Access time_series via `{{ nodes.historical.value.time_series }}` — the value port is a dict, not a plain list",
|
|
180
|
-
"
|
|
220
|
+
"Minute intervals are 1m, 5m, 10m, 15m, 30m and 60m (not 1h or 1min); daily bars are never a substitute for an intraday strategy",
|
|
221
|
+
"Minute bars include every session LS returns: pre-market, after-hours and the overnight session from 20:00 local time of the previous day (a Sunday date can appear); the regular session is 09:30-16:00 exchange local time, so filter by the time part of `date` when a strategy needs the regular session only",
|
|
222
|
+
"`adjust` has no effect on minute intervals: LS g3203 has no adjusted-price option, minute bars are the prices as traded",
|
|
181
223
|
"adjust=False (default) returns unadjusted prices; set True when comparing long-term price levels across splits",
|
|
182
|
-
"The
|
|
224
|
+
"The executor requests one g3204 or g3203 page; it does not expose response-time, continuation-completion or candle-finality evidence (the newest minute bar may still be forming)",
|
|
183
225
|
],
|
|
184
226
|
}
|
|
185
227
|
|
|
@@ -300,12 +342,18 @@ class OverseasStockHistoricalDataNode(BaseNode):
|
|
|
300
342
|
description="i18n:fields.OverseasStockHistoricalDataNode.interval",
|
|
301
343
|
default="1d",
|
|
302
344
|
required=True,
|
|
303
|
-
enum_values=["1d", "1w", "1M", "1Y"],
|
|
345
|
+
enum_values=["1d", "1w", "1M", "1Y", "1m", "5m", "10m", "15m", "30m", "60m"],
|
|
304
346
|
enum_labels={
|
|
305
347
|
"1d": "i18n:enums.interval.1d",
|
|
306
348
|
"1w": "i18n:enums.interval.1w",
|
|
307
349
|
"1M": "i18n:enums.interval.1M",
|
|
308
|
-
"1Y": "i18n:enums.interval.1Y"
|
|
350
|
+
"1Y": "i18n:enums.interval.1Y",
|
|
351
|
+
"1m": "i18n:enums.interval.1m",
|
|
352
|
+
"5m": "i18n:enums.interval.5m",
|
|
353
|
+
"10m": "i18n:enums.interval.10m",
|
|
354
|
+
"15m": "i18n:enums.interval.15m",
|
|
355
|
+
"30m": "i18n:enums.interval.30m",
|
|
356
|
+
"60m": "i18n:enums.interval.60m",
|
|
309
357
|
},
|
|
310
358
|
category=FieldCategory.PARAMETERS,
|
|
311
359
|
expression_mode=ExpressionMode.FIXED_ONLY,
|
|
@@ -5,7 +5,7 @@ authors = [
|
|
|
5
5
|
homepage = "https://programgarden.com"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
name = "programgarden-core"
|
|
8
|
-
version = "
|
|
8
|
+
version = "3.0.0"
|
|
9
9
|
license = "AGPL-3.0-or-later"
|
|
10
10
|
description = "ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의"
|
|
11
11
|
readme = "README.md"
|
|
@@ -1,419 +0,0 @@
|
|
|
1
|
-
"""ProgramGarden Core — CodeNode static compilation & AST security screening.
|
|
2
|
-
|
|
3
|
-
This module is the single source of truth for the CodeNode security lists and
|
|
4
|
-
the compile/screen pipeline. It is intentionally dependency-free (stdlib only)
|
|
5
|
-
so it can be reused verbatim by:
|
|
6
|
-
|
|
7
|
-
1. `WorkflowResolver.validate()` — pre-flight structured errors before run.
|
|
8
|
-
2. `CodeNodeExecutor` / subprocess worker — screen again right before `exec`.
|
|
9
|
-
3. An external web-platform "share gate" server — re-run the same hard AST scan.
|
|
10
|
-
|
|
11
|
-
Security model (see plan §2): CodeNode runs untrusted Python. Defense is layered:
|
|
12
|
-
|
|
13
|
-
* Layer 1 — scrubbed context (runtime, programgarden): the code never receives
|
|
14
|
-
credential accessors.
|
|
15
|
-
* Layer 2 — restricted builtins + AST denylist (THIS module): a curated
|
|
16
|
-
`__builtins__` (no eval/exec/open/getattr/...), a safe `__import__` gated by a
|
|
17
|
-
whitelist, and a static AST scan that rejects dangerous imports, builtin
|
|
18
|
-
calls, and introspection-escape dunders BEFORE any code runs.
|
|
19
|
-
* Layer 3 — binding seal (runtime, resolver): credentials cannot flow into
|
|
20
|
-
`data`/`params`.
|
|
21
|
-
* Layer 4 — subprocess isolation (runtime, programgarden): the code always runs
|
|
22
|
-
in a credential-free child process.
|
|
23
|
-
|
|
24
|
-
`compile_code_node` NEVER executes the code — it only parses, screens, and
|
|
25
|
-
compiles. Execution (exec + calling `execute`) is the runtime's responsibility.
|
|
26
|
-
|
|
27
|
-
All user-facing strings are English (AI-chatbot consumer contract).
|
|
28
|
-
"""
|
|
29
|
-
from __future__ import annotations
|
|
30
|
-
|
|
31
|
-
import ast
|
|
32
|
-
import builtins as _builtins
|
|
33
|
-
from dataclasses import dataclass, field
|
|
34
|
-
from types import CodeType
|
|
35
|
-
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
36
|
-
|
|
37
|
-
# Name of the entry-point coroutine the code text must define.
|
|
38
|
-
EXECUTE_FN_NAME = "execute"
|
|
39
|
-
|
|
40
|
-
# ⚠️ SECURITY POSTURE — READ THIS.
|
|
41
|
-
# AST screening + restricted builtins CANNOT be a sound sandbox for CPython.
|
|
42
|
-
# A determined attacker who can run Python can, in the general case, escape any
|
|
43
|
-
# in-process denylist (this is a well-known result; see the pysandbox author's
|
|
44
|
-
# own withdrawal). An adversarial audit of an earlier version of this file
|
|
45
|
-
# reproduced RCE via `operator.attrgetter`, `random._os`, and string-obfuscated
|
|
46
|
-
# dunders. The rules below are HARDENED defense-in-depth that close every
|
|
47
|
-
# demonstrated vector and raise the bar substantially — but they are NOT a
|
|
48
|
-
# containment guarantee. TRUE containment of untrusted code is the runtime's
|
|
49
|
-
# job (subprocess with no in-memory keys) + the operator's job (per-user pod
|
|
50
|
-
# isolation, no-egress NetworkPolicy, restricted file perms, scrubbed env).
|
|
51
|
-
# Treat CodeNode as "run trusted code, defense-in-depth against mistakes and
|
|
52
|
-
# opportunistic abuse", not "safe to run arbitrary hostile code".
|
|
53
|
-
|
|
54
|
-
# ── Whitelist: modules importable from CodeNode code ────────────────────────
|
|
55
|
-
# Pure-computation stdlib only. `operator` is deliberately EXCLUDED — its
|
|
56
|
-
# attrgetter/methodcaller are getattr/dispatch substitutes. Modules that
|
|
57
|
-
# re-export os/sys as public attributes (e.g. random._os, uuid.os) are still
|
|
58
|
-
# covered by the attribute screen below, not by this set. The embedding app may
|
|
59
|
-
# EXTEND this via `allowed_imports=` (e.g. numpy) but the default is minimal.
|
|
60
|
-
DEFAULT_ALLOWED_IMPORTS: frozenset = frozenset({
|
|
61
|
-
"math", "cmath", "statistics", "decimal", "fractions", "numbers",
|
|
62
|
-
"random", "json", "re", "datetime", "time", "calendar",
|
|
63
|
-
"collections", "itertools", "functools",
|
|
64
|
-
"string", "textwrap", "unicodedata",
|
|
65
|
-
"heapq", "bisect", "array", "copy",
|
|
66
|
-
"dataclasses", "enum", "typing",
|
|
67
|
-
"hashlib", "hmac", "base64", "binascii", "zlib",
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
# ── Denylist: modules called out for a clearer error message ────────────────
|
|
71
|
-
# Enforcement is whitelist-based (not in DEFAULT_ALLOWED_IMPORTS → blocked);
|
|
72
|
-
# this set only makes the message explicit for well-known dangerous modules.
|
|
73
|
-
FORBIDDEN_MODULES: frozenset = frozenset({
|
|
74
|
-
"os", "sys", "subprocess", "socket", "shutil", "importlib", "imp",
|
|
75
|
-
"ctypes", "cffi", "gc", "inspect", "pathlib", "glob", "tempfile", "io",
|
|
76
|
-
"urllib", "http", "requests", "ftplib", "smtplib", "asyncio",
|
|
77
|
-
"multiprocessing", "threading", "concurrent", "_thread", "operator",
|
|
78
|
-
"builtins", "marshal", "pickle", "shelve", "dbm", "sqlite3",
|
|
79
|
-
"code", "codeop", "pty", "resource", "signal", "mmap", "fcntl",
|
|
80
|
-
"platform", "webbrowser", "ssl", "select", "selectors", "uuid",
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
# ── Denylist: builtin names that must never be called or aliased ────────────
|
|
84
|
-
FORBIDDEN_BUILTINS: frozenset = frozenset({
|
|
85
|
-
"eval", "exec", "compile", "open", "__import__",
|
|
86
|
-
"getattr", "setattr", "delattr",
|
|
87
|
-
"globals", "locals", "vars",
|
|
88
|
-
"input", "breakpoint", "help", "memoryview",
|
|
89
|
-
"exit", "quit", "__build_class__",
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
# ── Denylist: dangerous PUBLIC attribute names (module re-exports & escapes) ──
|
|
93
|
-
# Many stdlib modules re-export `os`/`sys` as public attributes (statistics.sys,
|
|
94
|
-
# uuid.os, ...) — walking those reaches os.system. `mro`/`modules`/`system`/etc.
|
|
95
|
-
# are the follow-on hops. Blocking these attribute NAMES (in addition to the
|
|
96
|
-
# blanket underscore rule below) severs the module-attribute escape path.
|
|
97
|
-
FORBIDDEN_ATTR_NAMES: frozenset = frozenset({
|
|
98
|
-
"os", "sys", "subprocess", "socket", "builtins", "importlib",
|
|
99
|
-
"modules", "system", "popen", "spawn", "spawnl", "spawnv", "spawnve",
|
|
100
|
-
"fork", "forkpty", "execv", "execve", "execl", "execlp", "execvp",
|
|
101
|
-
"mro", "import_module", "load_module", "reload", "getattr", "setattr",
|
|
102
|
-
"delattr", "globals", "environ", "getenv", "putenv", "connect",
|
|
103
|
-
"urlopen", "call", "run", "check_output", "Popen", "loader", "spec",
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
# ── Denylist: introspection-escape / exfiltration dunders & frame attrs ──────
|
|
107
|
-
# NOTE: enforcement is the underscore rule in _scan_ast — any ATTRIBUTE beginning
|
|
108
|
-
# with '_' is rejected (subsumes every dunder and private module re-export like
|
|
109
|
-
# random._os); STRING literals are rejected when they contain a dunder or name a
|
|
110
|
-
# blocked attribute re-export (bare single-underscore data-key strings such as
|
|
111
|
-
# "_partial_failure" are allowed). This explicit set is retained for message clarity.
|
|
112
|
-
FORBIDDEN_DUNDERS: frozenset = frozenset({
|
|
113
|
-
"__class__", "__bases__", "__base__", "__subclasses__", "__mro__",
|
|
114
|
-
"__globals__", "__code__", "__closure__", "__func__", "__self__",
|
|
115
|
-
"__dict__", "__getattribute__", "__setattr__", "__delattr__",
|
|
116
|
-
"__reduce__", "__reduce_ex__", "__builtins__", "__import__",
|
|
117
|
-
"__loader__", "__spec__", "__init_subclass__", "__subclasshook__",
|
|
118
|
-
"__getattr__", "__wrapped__", "__objclass__",
|
|
119
|
-
"gi_frame", "gi_code", "cr_frame", "cr_code", "ag_frame",
|
|
120
|
-
"f_back", "f_globals", "f_locals", "f_builtins", "tb_frame",
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
# Format replacement-field that performs attribute access, e.g. "{0.__class__}"
|
|
124
|
-
# or "{x.sys}" — used to walk attributes via str.format without an AST Attribute
|
|
125
|
-
# node. Rejected wholesale in string constants.
|
|
126
|
-
import re as _re
|
|
127
|
-
_FORMAT_ATTR_RE = _re.compile(r"\{[^{}]*\.[^{}]*\}")
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@dataclass
|
|
131
|
-
class CodeScreenResult:
|
|
132
|
-
"""Structured outcome of `compile_code_node`. `ok=False` carries an
|
|
133
|
-
AI-chatbot-consumable error (code + message + suggestion + location)."""
|
|
134
|
-
|
|
135
|
-
ok: bool
|
|
136
|
-
code_object: Optional[CodeType] = None
|
|
137
|
-
error_code: Optional[str] = None # one of CODE_NODE_SYNTAX_ERROR / _FORBIDDEN / _NO_EXECUTE
|
|
138
|
-
message: Optional[str] = None
|
|
139
|
-
suggestion: Optional[str] = None
|
|
140
|
-
line: Optional[int] = None
|
|
141
|
-
offset: Optional[int] = None
|
|
142
|
-
details: Dict[str, Any] = field(default_factory=dict)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def _module_root(name: Optional[str]) -> str:
|
|
146
|
-
return (name or "").split(".")[0]
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def _scan_ast(tree: ast.AST, allowed_imports: Set[str]) -> List[Dict[str, Any]]:
|
|
150
|
-
"""Return a list of violation dicts (empty when clean).
|
|
151
|
-
|
|
152
|
-
Each violation: {kind, name, line, col, reason}. `kind` ∈
|
|
153
|
-
{import, builtin, dunder}.
|
|
154
|
-
"""
|
|
155
|
-
violations: List[Dict[str, Any]] = []
|
|
156
|
-
|
|
157
|
-
for node in ast.walk(tree):
|
|
158
|
-
# ── Imports (whitelist-enforced) ──
|
|
159
|
-
if isinstance(node, ast.Import):
|
|
160
|
-
for alias in node.names:
|
|
161
|
-
root = _module_root(alias.name)
|
|
162
|
-
if root not in allowed_imports:
|
|
163
|
-
reason = (
|
|
164
|
-
f"module '{alias.name}' is blocked (filesystem/process/network access)"
|
|
165
|
-
if root in FORBIDDEN_MODULES
|
|
166
|
-
else f"module '{alias.name}' is not in the allowed import list"
|
|
167
|
-
)
|
|
168
|
-
violations.append({
|
|
169
|
-
"kind": "import", "name": alias.name,
|
|
170
|
-
"line": node.lineno, "col": node.col_offset, "reason": reason,
|
|
171
|
-
})
|
|
172
|
-
elif isinstance(node, ast.ImportFrom):
|
|
173
|
-
root = _module_root(node.module)
|
|
174
|
-
# `from . import x` (level>0) is always blocked — no package context.
|
|
175
|
-
if node.level and node.level > 0:
|
|
176
|
-
violations.append({
|
|
177
|
-
"kind": "import", "name": node.module or ".",
|
|
178
|
-
"line": node.lineno, "col": node.col_offset,
|
|
179
|
-
"reason": "relative imports are not allowed in CodeNode",
|
|
180
|
-
})
|
|
181
|
-
elif root not in allowed_imports:
|
|
182
|
-
reason = (
|
|
183
|
-
f"module '{node.module}' is blocked (filesystem/process/network access)"
|
|
184
|
-
if root in FORBIDDEN_MODULES
|
|
185
|
-
else f"module '{node.module}' is not in the allowed import list"
|
|
186
|
-
)
|
|
187
|
-
violations.append({
|
|
188
|
-
"kind": "import", "name": node.module or "",
|
|
189
|
-
"line": node.lineno, "col": node.col_offset, "reason": reason,
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
# ── Forbidden builtin names (call or alias) ──
|
|
193
|
-
elif isinstance(node, ast.Name):
|
|
194
|
-
if node.id in FORBIDDEN_BUILTINS:
|
|
195
|
-
violations.append({
|
|
196
|
-
"kind": "builtin", "name": node.id,
|
|
197
|
-
"line": node.lineno, "col": node.col_offset,
|
|
198
|
-
"reason": f"use of '{node.id}' is not allowed",
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
# ── Attribute access screen ──
|
|
202
|
-
# (1) BLANKET: any attribute beginning with '_' is rejected — this
|
|
203
|
-
# subsumes every dunder (__class__/__globals__/__subclasses__/...)
|
|
204
|
-
# AND private module re-exports (random._os, random._urandom, ...).
|
|
205
|
-
# (2) A denylist of dangerous PUBLIC names (module re-exports & shell
|
|
206
|
-
# hops): statistics.sys, uuid.os, x.system, x.modules, type().mro().
|
|
207
|
-
elif isinstance(node, ast.Attribute):
|
|
208
|
-
if node.attr.startswith("_"):
|
|
209
|
-
violations.append({
|
|
210
|
-
"kind": "dunder", "name": node.attr,
|
|
211
|
-
"line": node.lineno, "col": node.col_offset,
|
|
212
|
-
"reason": f"access to underscore attribute '{node.attr}' is not allowed (introspection escape)",
|
|
213
|
-
})
|
|
214
|
-
elif node.attr in FORBIDDEN_ATTR_NAMES:
|
|
215
|
-
violations.append({
|
|
216
|
-
"kind": "attr", "name": node.attr,
|
|
217
|
-
"line": node.lineno, "col": node.col_offset,
|
|
218
|
-
"reason": f"attribute '{node.attr}' access is not allowed (module/escape re-export)",
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
# ── String-constant screen ──
|
|
222
|
-
# Defeats attribute-walk via strings: getattr/attrgetter args, str.format
|
|
223
|
-
# attribute fields, subscript-string dunder access. Reject strings that
|
|
224
|
-
# start with '_', contain a dunder, name a dangerous attribute, or embed
|
|
225
|
-
# a format field with attribute access.
|
|
226
|
-
elif isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
227
|
-
v = node.value
|
|
228
|
-
bad = None
|
|
229
|
-
# A single leading underscore names a documented native data marker
|
|
230
|
-
# read as a dict key (e.g. balance["_partial_failure"], "_source"),
|
|
231
|
-
# so it is allowed. Still rejected: dunder-style names, and blocked
|
|
232
|
-
# public/underscored attribute re-exports ("os"/"sys"/"system"/"_os"/
|
|
233
|
-
# "_sys"... -> in FORBIDDEN_ATTR_NAMES via lstrip("_")), and attribute-
|
|
234
|
-
# walking format fields. getattr/attrgetter/operator/format are
|
|
235
|
-
# independently blocked, so a bare underscore data-key string cannot
|
|
236
|
-
# be turned into attribute access.
|
|
237
|
-
if "__" in v:
|
|
238
|
-
bad = "string literal contains a dunder (introspection escape)"
|
|
239
|
-
elif v.lstrip("_") in FORBIDDEN_ATTR_NAMES:
|
|
240
|
-
bad = f"string literal '{v}' names a blocked attribute (introspection escape)"
|
|
241
|
-
elif _FORMAT_ATTR_RE.search(v):
|
|
242
|
-
bad = "format string performs attribute access (introspection escape)"
|
|
243
|
-
if bad:
|
|
244
|
-
violations.append({
|
|
245
|
-
"kind": "string", "name": v[:40],
|
|
246
|
-
"line": node.lineno, "col": node.col_offset,
|
|
247
|
-
"reason": bad,
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
return violations
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def _has_execute(tree: ast.Module) -> bool:
|
|
254
|
-
for node in tree.body:
|
|
255
|
-
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == EXECUTE_FN_NAME:
|
|
256
|
-
return True
|
|
257
|
-
return False
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def compile_code_node(
|
|
261
|
-
code: str,
|
|
262
|
-
node_id: str,
|
|
263
|
-
*,
|
|
264
|
-
screen: bool = True,
|
|
265
|
-
allowed_imports: Optional[Set[str]] = None,
|
|
266
|
-
) -> CodeScreenResult:
|
|
267
|
-
"""Parse, security-screen, and compile CodeNode source WITHOUT executing it.
|
|
268
|
-
|
|
269
|
-
Pipeline:
|
|
270
|
-
1. `ast.parse` — SyntaxError → CODE_NODE_SYNTAX_ERROR (line/offset).
|
|
271
|
-
2. AST denylist scan (when `screen=True`) — CODE_NODE_FORBIDDEN.
|
|
272
|
-
3. `execute` function must be defined — CODE_NODE_NO_EXECUTE.
|
|
273
|
-
4. `compile(filename=f'<code:{node_id}>')` — preserves traceback lines.
|
|
274
|
-
|
|
275
|
-
Args:
|
|
276
|
-
code: Python source text (must define `execute`).
|
|
277
|
-
node_id: node id, used only for the compiled filename (traceback anchor).
|
|
278
|
-
screen: run the AST denylist scan (always True in production).
|
|
279
|
-
allowed_imports: override the import whitelist (defaults to
|
|
280
|
-
DEFAULT_ALLOWED_IMPORTS). Enforced both here and by the runtime
|
|
281
|
-
safe `__import__`.
|
|
282
|
-
|
|
283
|
-
Returns:
|
|
284
|
-
CodeScreenResult — `ok=True` with `code_object`, or `ok=False` with a
|
|
285
|
-
structured, English, chatbot-consumable error. Never raises for
|
|
286
|
-
screening/syntax failures (they are returned as data).
|
|
287
|
-
"""
|
|
288
|
-
allowed = set(allowed_imports) if allowed_imports is not None else set(DEFAULT_ALLOWED_IMPORTS)
|
|
289
|
-
|
|
290
|
-
if not isinstance(code, str) or not code.strip():
|
|
291
|
-
return CodeScreenResult(
|
|
292
|
-
ok=False,
|
|
293
|
-
error_code="CODE_NODE_SYNTAX_ERROR",
|
|
294
|
-
message="CodeNode 'code' is empty.",
|
|
295
|
-
suggestion="Provide Python source that defines `async def execute(data, params, context)`.",
|
|
296
|
-
line=1, offset=0,
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
# 1. Parse
|
|
300
|
-
try:
|
|
301
|
-
tree = ast.parse(code, filename=f"<code:{node_id}>", mode="exec")
|
|
302
|
-
except SyntaxError as e:
|
|
303
|
-
return CodeScreenResult(
|
|
304
|
-
ok=False,
|
|
305
|
-
error_code="CODE_NODE_SYNTAX_ERROR",
|
|
306
|
-
message=f"CodeNode syntax error: {e.msg}",
|
|
307
|
-
suggestion="Fix the Python syntax at the reported line/offset.",
|
|
308
|
-
line=e.lineno,
|
|
309
|
-
offset=e.offset,
|
|
310
|
-
details={"raw": str(e)},
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
# 2. Screen
|
|
314
|
-
if screen:
|
|
315
|
-
violations = _scan_ast(tree, allowed)
|
|
316
|
-
if violations:
|
|
317
|
-
first = violations[0]
|
|
318
|
-
names = sorted({v["name"] for v in violations})
|
|
319
|
-
return CodeScreenResult(
|
|
320
|
-
ok=False,
|
|
321
|
-
error_code="CODE_NODE_FORBIDDEN",
|
|
322
|
-
message=(
|
|
323
|
-
f"CodeNode uses forbidden construct(s): {', '.join(names)} "
|
|
324
|
-
f"({first['reason']})."
|
|
325
|
-
),
|
|
326
|
-
suggestion=(
|
|
327
|
-
"Remove the blocked import/builtin/introspection. CodeNode allows "
|
|
328
|
-
"pure-computation stdlib only (math, statistics, json, datetime, ...); "
|
|
329
|
-
"credentials, filesystem, network, subprocess, and object-graph "
|
|
330
|
-
"introspection are not available."
|
|
331
|
-
),
|
|
332
|
-
line=first["line"],
|
|
333
|
-
offset=first["col"],
|
|
334
|
-
details={"violations": violations, "forbidden_names": names},
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
# 3. execute() must exist
|
|
338
|
-
if not _has_execute(tree):
|
|
339
|
-
return CodeScreenResult(
|
|
340
|
-
ok=False,
|
|
341
|
-
error_code="CODE_NODE_NO_EXECUTE",
|
|
342
|
-
message=f"CodeNode code must define a function named '{EXECUTE_FN_NAME}'.",
|
|
343
|
-
suggestion=(
|
|
344
|
-
"Define `async def execute(data, params, context):` (a plain "
|
|
345
|
-
"`def execute(...)` is also accepted and auto-wrapped)."
|
|
346
|
-
),
|
|
347
|
-
line=1, offset=0,
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
# 4. Compile (never exec here)
|
|
351
|
-
try:
|
|
352
|
-
code_object = compile(tree, filename=f"<code:{node_id}>", mode="exec")
|
|
353
|
-
except (SyntaxError, ValueError) as e: # e.g. too-deeply-nested
|
|
354
|
-
return CodeScreenResult(
|
|
355
|
-
ok=False,
|
|
356
|
-
error_code="CODE_NODE_SYNTAX_ERROR",
|
|
357
|
-
message=f"CodeNode failed to compile: {e}",
|
|
358
|
-
suggestion="Simplify the code so it compiles as a module.",
|
|
359
|
-
line=getattr(e, "lineno", 1) or 1,
|
|
360
|
-
offset=getattr(e, "offset", 0) or 0,
|
|
361
|
-
details={"raw": str(e)},
|
|
362
|
-
)
|
|
363
|
-
|
|
364
|
-
return CodeScreenResult(ok=True, code_object=code_object)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
# The real import machinery, captured once so the sandbox can delegate to it
|
|
368
|
-
# after the whitelist check.
|
|
369
|
-
_REAL_IMPORT = _builtins.__import__
|
|
370
|
-
|
|
371
|
-
# Curated safe builtins — everything a pure-compute function needs, and nothing
|
|
372
|
-
# that grants escape (no eval/exec/compile/open/getattr/setattr/globals/...).
|
|
373
|
-
_SAFE_BUILTIN_NAMES: Tuple[str, ...] = (
|
|
374
|
-
"abs", "all", "any", "ascii", "bin", "bool", "bytearray", "bytes",
|
|
375
|
-
"callable", "chr", "complex", "dict", "divmod", "enumerate", "filter",
|
|
376
|
-
"float", "format", "frozenset", "hash", "hex", "int", "isinstance",
|
|
377
|
-
"issubclass", "iter", "len", "list", "map", "max", "min", "next", "oct",
|
|
378
|
-
"ord", "pow", "print", "range", "repr", "reversed", "round", "set",
|
|
379
|
-
"slice", "sorted", "str", "sum", "tuple", "type", "zip", "hasattr",
|
|
380
|
-
# exceptions & sentinels
|
|
381
|
-
"Exception", "BaseException", "ValueError", "TypeError", "KeyError",
|
|
382
|
-
"IndexError", "AttributeError", "ZeroDivisionError", "ArithmeticError",
|
|
383
|
-
"OverflowError", "RuntimeError", "StopIteration", "StopAsyncIteration",
|
|
384
|
-
"NotImplementedError", "AssertionError", "LookupError", "NameError",
|
|
385
|
-
"RecursionError", "FloatingPointError", "GeneratorExit", "KeyboardInterrupt",
|
|
386
|
-
"True", "False", "None", "NotImplemented", "Ellipsis",
|
|
387
|
-
"object", "property", "staticmethod", "classmethod", "super",
|
|
388
|
-
# __build_class__ is required for `class` statements in user code; it is not
|
|
389
|
-
# itself an escape vector (dunder screen blocks the dangerous follow-ups).
|
|
390
|
-
"__build_class__",
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
def build_restricted_builtins(allowed_imports: Optional[Set[str]] = None) -> Dict[str, Any]:
|
|
395
|
-
"""Build the `__builtins__` dict used when exec-ing CodeNode source.
|
|
396
|
-
|
|
397
|
-
Contains only the curated safe builtins plus a whitelist-gated
|
|
398
|
-
`__import__`. Pure function (no exec, stdlib only) so it lives in core and
|
|
399
|
-
is reused by the runtime worker.
|
|
400
|
-
"""
|
|
401
|
-
allowed = set(allowed_imports) if allowed_imports is not None else set(DEFAULT_ALLOWED_IMPORTS)
|
|
402
|
-
|
|
403
|
-
def _safe_import(name, globals=None, locals=None, fromlist=(), level=0):
|
|
404
|
-
if level and level != 0:
|
|
405
|
-
raise ImportError("relative imports are not allowed in CodeNode")
|
|
406
|
-
root = _module_root(name)
|
|
407
|
-
if root not in allowed:
|
|
408
|
-
raise ImportError(
|
|
409
|
-
f"import of '{name}' is not allowed in CodeNode "
|
|
410
|
-
f"(allowed: pure-computation stdlib only)"
|
|
411
|
-
)
|
|
412
|
-
return _REAL_IMPORT(name, globals, locals, fromlist, level)
|
|
413
|
-
|
|
414
|
-
safe: Dict[str, Any] = {}
|
|
415
|
-
for bn in _SAFE_BUILTIN_NAMES:
|
|
416
|
-
if hasattr(_builtins, bn):
|
|
417
|
-
safe[bn] = getattr(_builtins, bn)
|
|
418
|
-
safe["__import__"] = _safe_import
|
|
419
|
-
return safe
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/bases/components.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/expression/__init__.py
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/expression/evaluator.py
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/expression/reference.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/connection_rule.py
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/credential.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/field_binding.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/order_diagnostics.py
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/plugin_resource.py
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/resilience.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/models/validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/account_futures.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/account_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/backtest_futures.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/calculation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/data_futures.py
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/data_korea_stock.py
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/data_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/fundamental_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/open_orders_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/ranking_stock.py
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/realtime_futures.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/realtime_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/session_gate.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol_futures.py
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol_korea_stock.py
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/presets/__init__.py
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/presets/news_analyst.json
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/presets/risk_manager.json
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/presets/strategist.json
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/registry/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/registry/node_registry.py
RENAMED
|
File without changes
|
{programgarden_core-2.6.3 → programgarden_core-3.0.0}/programgarden_core/registry/plugin_registry.py
RENAMED
|
File without changes
|
|
File without changes
|