kailash 0.3.0__tar.gz → 0.3.2__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.
- {kailash-0.3.0/src/kailash.egg-info → kailash-0.3.2}/PKG-INFO +53 -5
- {kailash-0.3.0 → kailash-0.3.2}/README.md +52 -4
- {kailash-0.3.0 → kailash-0.3.2}/pyproject.toml +17 -1
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/access_control.py +40 -39
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/api/auth.py +26 -32
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/api/custom_nodes.py +29 -29
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/api/custom_nodes_secure.py +35 -35
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/api/database.py +17 -17
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/api/gateway.py +19 -19
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/api/mcp_integration.py +24 -23
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/api/studio.py +45 -45
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/api/workflow_api.py +8 -8
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/cli/commands.py +5 -8
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/manifest.py +42 -42
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/mcp/__init__.py +1 -1
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/mcp/ai_registry_server.py +20 -20
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/mcp/client.py +9 -11
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/mcp/client_new.py +10 -10
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/mcp/server.py +1 -2
- kailash-0.3.2/src/kailash/mcp/server_enhanced.py +449 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/mcp/servers/ai_registry.py +6 -6
- kailash-0.3.2/src/kailash/mcp/utils/__init__.py +31 -0
- kailash-0.3.2/src/kailash/mcp/utils/cache.py +267 -0
- kailash-0.3.2/src/kailash/mcp/utils/config.py +263 -0
- kailash-0.3.2/src/kailash/mcp/utils/formatters.py +293 -0
- kailash-0.3.2/src/kailash/mcp/utils/metrics.py +418 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/ai/agents.py +9 -9
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/ai/ai_providers.py +33 -34
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/ai/embedding_generator.py +31 -32
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/ai/intelligent_agent_orchestrator.py +62 -66
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/ai/iterative_llm_agent.py +48 -48
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/ai/llm_agent.py +32 -33
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/ai/models.py +13 -13
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/ai/self_organizing.py +44 -44
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/api/auth.py +11 -11
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/api/graphql.py +13 -13
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/api/http.py +19 -19
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/api/monitoring.py +20 -20
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/api/rate_limiting.py +9 -13
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/api/rest.py +29 -29
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/api/security.py +44 -47
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/base.py +21 -23
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/base_async.py +7 -7
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/base_cycle_aware.py +12 -12
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/base_with_acl.py +5 -5
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/code/python.py +66 -57
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/data/directory.py +6 -6
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/data/event_generation.py +10 -10
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/data/file_discovery.py +28 -31
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/data/readers.py +8 -8
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/data/retrieval.py +10 -10
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/data/sharepoint_graph.py +17 -17
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/data/sources.py +5 -5
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/data/sql.py +13 -13
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/data/streaming.py +25 -25
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/data/vector_db.py +22 -22
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/data/writers.py +7 -7
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/logic/async_operations.py +17 -17
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/logic/convergence.py +11 -11
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/logic/loop.py +4 -4
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/logic/operations.py +11 -11
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/logic/workflow.py +8 -9
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/mixins/mcp.py +17 -17
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/mixins.py +8 -10
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/transform/chunkers.py +3 -3
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/transform/formatters.py +7 -7
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/transform/processors.py +10 -10
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/runtime/access_controlled.py +18 -18
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/runtime/async_local.py +17 -19
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/runtime/docker.py +20 -22
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/runtime/local.py +16 -16
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/runtime/parallel.py +23 -23
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/runtime/parallel_cyclic.py +27 -27
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/runtime/runner.py +6 -6
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/runtime/testing.py +20 -20
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/sdk_exceptions.py +0 -58
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/security.py +14 -26
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/tracking/manager.py +38 -38
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/tracking/metrics_collector.py +15 -14
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/tracking/models.py +53 -53
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/tracking/storage/base.py +7 -17
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/tracking/storage/database.py +22 -23
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/tracking/storage/filesystem.py +38 -40
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/utils/export.py +21 -21
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/utils/templates.py +2 -3
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/visualization/api.py +30 -34
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/visualization/dashboard.py +17 -17
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/visualization/performance.py +16 -16
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/visualization/reports.py +25 -27
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/builder.py +8 -8
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/convergence.py +13 -12
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/cycle_analyzer.py +30 -32
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/cycle_builder.py +12 -12
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/cycle_config.py +16 -15
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/cycle_debugger.py +40 -40
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/cycle_exceptions.py +29 -29
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/cycle_profiler.py +21 -21
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/cycle_state.py +20 -22
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/cyclic_runner.py +44 -44
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/graph.py +40 -40
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/mermaid_visualizer.py +9 -11
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/migration.py +22 -22
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/mock_registry.py +6 -6
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/runner.py +9 -9
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/safety.py +12 -13
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/state.py +8 -11
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/templates.py +19 -19
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/validation.py +14 -14
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/visualization.py +22 -22
- {kailash-0.3.0 → kailash-0.3.2/src/kailash.egg-info}/PKG-INFO +53 -5
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash.egg-info/SOURCES.txt +6 -0
- {kailash-0.3.0 → kailash-0.3.2}/LICENSE +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/MANIFEST.in +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/setup.cfg +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/setup.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/__main__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/api/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/api/__main__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/api/studio_secure.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/cli/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/mcp/__main__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/mcp/server_new.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/mcp/servers/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/ai/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/ai/a2a.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/api/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/code/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/data/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/logic/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/mixins/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/nodes/transform/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/runtime/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/tracking/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/tracking/storage/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/utils/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/visualization/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash/workflow/__init__.py +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash.egg-info/dependency_links.txt +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash.egg-info/entry_points.txt +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash.egg-info/not-zip-safe +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash.egg-info/requires.txt +0 -0
- {kailash-0.3.0 → kailash-0.3.2}/src/kailash.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: kailash
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.2
|
4
4
|
Summary: Python SDK for the Kailash container-node architecture
|
5
5
|
Home-page: https://github.com/integrum/kailash-python-sdk
|
6
6
|
Author: Integrum
|
@@ -109,7 +109,8 @@ Dynamic: requires-python
|
|
109
109
|
- 🔁 **Cyclic Workflows (v0.2.0)**: Universal Hybrid Cyclic Graph Architecture with 30,000+ iterations/second performance
|
110
110
|
- 🛠️ **Developer Tools**: CycleAnalyzer, CycleDebugger, CycleProfiler for production-ready cyclic workflows
|
111
111
|
- 📈 **High Performance**: Optimized execution engine supporting 100,000+ iteration workflows
|
112
|
-
- 📁 **
|
112
|
+
- 📁 **Complete Finance Workflow Library (v0.3.1)**: Production-ready financial workflows with AI analysis
|
113
|
+
- 💼 **Enterprise Workflow Patterns**: Credit risk, portfolio optimization, trading signals, fraud detection
|
113
114
|
|
114
115
|
## 🎯 Who Is This For?
|
115
116
|
|
@@ -197,6 +198,52 @@ from kailash.utils.export import export_workflow
|
|
197
198
|
export_workflow(workflow, "customer_analysis.yaml")
|
198
199
|
```
|
199
200
|
|
201
|
+
## 💼 Finance Workflow Library (New in v0.3.1)
|
202
|
+
|
203
|
+
Complete production-ready financial workflows using AI and modern quantitative methods:
|
204
|
+
|
205
|
+
### Credit Risk Assessment
|
206
|
+
|
207
|
+
```python
|
208
|
+
from kailash.workflow import Workflow
|
209
|
+
from kailash.nodes.data import CSVReaderNode
|
210
|
+
from kailash.nodes.code import PythonCodeNode
|
211
|
+
from kailash.nodes.ai import LLMAgentNode
|
212
|
+
|
213
|
+
def calculate_risk_metrics(customers, transactions):
|
214
|
+
"""Calculate comprehensive risk metrics."""
|
215
|
+
# Modern risk scoring with AI analysis
|
216
|
+
# 100+ lines of production risk calculation
|
217
|
+
return {"result": risk_scores}
|
218
|
+
|
219
|
+
workflow = Workflow("credit-risk", "Credit Risk Assessment")
|
220
|
+
workflow.add_node("customer_reader", CSVReaderNode())
|
221
|
+
workflow.add_node("risk_calculator", PythonCodeNode.from_function(func=calculate_risk_metrics))
|
222
|
+
workflow.add_node("ai_analyzer", LLMAgentNode(model="gpt-4",
|
223
|
+
system_prompt="You are a financial risk expert..."))
|
224
|
+
```
|
225
|
+
|
226
|
+
### Portfolio Optimization
|
227
|
+
|
228
|
+
```python
|
229
|
+
def optimize_portfolio(holdings, market_data, risk_profile="moderate"):
|
230
|
+
"""Modern Portfolio Theory optimization with rebalancing."""
|
231
|
+
# Sharpe ratio optimization, correlation analysis
|
232
|
+
# Risk-adjusted returns with AI market insights
|
233
|
+
return {"result": optimization_plan}
|
234
|
+
|
235
|
+
workflow = Workflow("portfolio-opt", "Portfolio Optimization")
|
236
|
+
workflow.add_node("optimizer", PythonCodeNode.from_function(func=optimize_portfolio))
|
237
|
+
# Generates rebalancing trades, risk metrics, AI market analysis
|
238
|
+
```
|
239
|
+
|
240
|
+
### Trading Signals & Fraud Detection
|
241
|
+
|
242
|
+
- **Trading Signals**: Technical indicators (RSI, MACD, Bollinger Bands) + AI sentiment
|
243
|
+
- **Fraud Detection**: Real-time transaction monitoring with velocity analysis
|
244
|
+
|
245
|
+
**See complete examples**: `sdk-users/workflows/by-industry/finance/`
|
246
|
+
|
200
247
|
## 📚 Documentation
|
201
248
|
|
202
249
|
### For SDK Users
|
@@ -204,9 +251,10 @@ export_workflow(workflow, "customer_analysis.yaml")
|
|
204
251
|
**Build solutions with the SDK:**
|
205
252
|
- `sdk-users/` - Everything you need to build with Kailash
|
206
253
|
- `developer/` - Node creation patterns and troubleshooting
|
207
|
-
- `workflows/` -
|
254
|
+
- `workflows/` - Complete production workflow library (v0.3.1)
|
255
|
+
- Finance workflows: Credit risk, portfolio optimization, trading signals, fraud detection
|
208
256
|
- Quick-start patterns (30-second workflows)
|
209
|
-
- Industry-specific solutions
|
257
|
+
- Industry-specific solutions by vertical
|
210
258
|
- Enterprise integration patterns
|
211
259
|
- `essentials/` - Quick reference and cheatsheets
|
212
260
|
- `nodes/` - Comprehensive node catalog (66+ nodes)
|
@@ -347,7 +395,7 @@ assert results["analyze"]["result"]["total_customers"] == len(test_data)
|
|
347
395
|
3. **Monitor in real-time**:
|
348
396
|
```python
|
349
397
|
from kailash.visualization import DashboardServer
|
350
|
-
|
398
|
+
|
351
399
|
server = DashboardServer(port=8080)
|
352
400
|
server.start()
|
353
401
|
# Open http://localhost:8080 for live monitoring
|
@@ -39,7 +39,8 @@
|
|
39
39
|
- 🔁 **Cyclic Workflows (v0.2.0)**: Universal Hybrid Cyclic Graph Architecture with 30,000+ iterations/second performance
|
40
40
|
- 🛠️ **Developer Tools**: CycleAnalyzer, CycleDebugger, CycleProfiler for production-ready cyclic workflows
|
41
41
|
- 📈 **High Performance**: Optimized execution engine supporting 100,000+ iteration workflows
|
42
|
-
- 📁 **
|
42
|
+
- 📁 **Complete Finance Workflow Library (v0.3.1)**: Production-ready financial workflows with AI analysis
|
43
|
+
- 💼 **Enterprise Workflow Patterns**: Credit risk, portfolio optimization, trading signals, fraud detection
|
43
44
|
|
44
45
|
## 🎯 Who Is This For?
|
45
46
|
|
@@ -127,6 +128,52 @@ from kailash.utils.export import export_workflow
|
|
127
128
|
export_workflow(workflow, "customer_analysis.yaml")
|
128
129
|
```
|
129
130
|
|
131
|
+
## 💼 Finance Workflow Library (New in v0.3.1)
|
132
|
+
|
133
|
+
Complete production-ready financial workflows using AI and modern quantitative methods:
|
134
|
+
|
135
|
+
### Credit Risk Assessment
|
136
|
+
|
137
|
+
```python
|
138
|
+
from kailash.workflow import Workflow
|
139
|
+
from kailash.nodes.data import CSVReaderNode
|
140
|
+
from kailash.nodes.code import PythonCodeNode
|
141
|
+
from kailash.nodes.ai import LLMAgentNode
|
142
|
+
|
143
|
+
def calculate_risk_metrics(customers, transactions):
|
144
|
+
"""Calculate comprehensive risk metrics."""
|
145
|
+
# Modern risk scoring with AI analysis
|
146
|
+
# 100+ lines of production risk calculation
|
147
|
+
return {"result": risk_scores}
|
148
|
+
|
149
|
+
workflow = Workflow("credit-risk", "Credit Risk Assessment")
|
150
|
+
workflow.add_node("customer_reader", CSVReaderNode())
|
151
|
+
workflow.add_node("risk_calculator", PythonCodeNode.from_function(func=calculate_risk_metrics))
|
152
|
+
workflow.add_node("ai_analyzer", LLMAgentNode(model="gpt-4",
|
153
|
+
system_prompt="You are a financial risk expert..."))
|
154
|
+
```
|
155
|
+
|
156
|
+
### Portfolio Optimization
|
157
|
+
|
158
|
+
```python
|
159
|
+
def optimize_portfolio(holdings, market_data, risk_profile="moderate"):
|
160
|
+
"""Modern Portfolio Theory optimization with rebalancing."""
|
161
|
+
# Sharpe ratio optimization, correlation analysis
|
162
|
+
# Risk-adjusted returns with AI market insights
|
163
|
+
return {"result": optimization_plan}
|
164
|
+
|
165
|
+
workflow = Workflow("portfolio-opt", "Portfolio Optimization")
|
166
|
+
workflow.add_node("optimizer", PythonCodeNode.from_function(func=optimize_portfolio))
|
167
|
+
# Generates rebalancing trades, risk metrics, AI market analysis
|
168
|
+
```
|
169
|
+
|
170
|
+
### Trading Signals & Fraud Detection
|
171
|
+
|
172
|
+
- **Trading Signals**: Technical indicators (RSI, MACD, Bollinger Bands) + AI sentiment
|
173
|
+
- **Fraud Detection**: Real-time transaction monitoring with velocity analysis
|
174
|
+
|
175
|
+
**See complete examples**: `sdk-users/workflows/by-industry/finance/`
|
176
|
+
|
130
177
|
## 📚 Documentation
|
131
178
|
|
132
179
|
### For SDK Users
|
@@ -134,9 +181,10 @@ export_workflow(workflow, "customer_analysis.yaml")
|
|
134
181
|
**Build solutions with the SDK:**
|
135
182
|
- `sdk-users/` - Everything you need to build with Kailash
|
136
183
|
- `developer/` - Node creation patterns and troubleshooting
|
137
|
-
- `workflows/` -
|
184
|
+
- `workflows/` - Complete production workflow library (v0.3.1)
|
185
|
+
- Finance workflows: Credit risk, portfolio optimization, trading signals, fraud detection
|
138
186
|
- Quick-start patterns (30-second workflows)
|
139
|
-
- Industry-specific solutions
|
187
|
+
- Industry-specific solutions by vertical
|
140
188
|
- Enterprise integration patterns
|
141
189
|
- `essentials/` - Quick reference and cheatsheets
|
142
190
|
- `nodes/` - Comprehensive node catalog (66+ nodes)
|
@@ -277,7 +325,7 @@ assert results["analyze"]["result"]["total_customers"] == len(test_data)
|
|
277
325
|
3. **Monitor in real-time**:
|
278
326
|
```python
|
279
327
|
from kailash.visualization import DashboardServer
|
280
|
-
|
328
|
+
|
281
329
|
server = DashboardServer(port=8080)
|
282
330
|
server.start()
|
283
331
|
# Open http://localhost:8080 for live monitoring
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "kailash"
|
7
|
-
version = "0.3.
|
7
|
+
version = "0.3.2"
|
8
8
|
description = "Python SDK for the Kailash container-node architecture"
|
9
9
|
authors = [
|
10
10
|
{name = "Integrum", email = "info@integrum.com"}
|
@@ -89,3 +89,19 @@ dev = [
|
|
89
89
|
|
90
90
|
[project.scripts]
|
91
91
|
kailash = "kailash.cli:main"
|
92
|
+
|
93
|
+
[tool.ruff]
|
94
|
+
line-length = 88
|
95
|
+
target-version = "py311"
|
96
|
+
|
97
|
+
[tool.ruff.lint]
|
98
|
+
select = ["E", "F"]
|
99
|
+
ignore = ["E203", "E501", "E722", "F401", "F841"]
|
100
|
+
|
101
|
+
[tool.ruff.lint.per-file-ignores]
|
102
|
+
"sdk-users/workflows/by-industry/finance/scripts/*.py" = ["E402"]
|
103
|
+
"examples/feature_examples/workflows/cyclic/*.py" = ["E722"]
|
104
|
+
"examples/feature_examples/workflows/parallel/*.py" = ["E722"]
|
105
|
+
"scripts/refactor-pythoncode-to-functions.py" = ["E722"]
|
106
|
+
"src/kailash/nodes/api/security.py" = ["E722"]
|
107
|
+
"tests/conftest_sdk_dev.py" = ["E722"]
|
@@ -51,10 +51,11 @@ Future Enhancements:
|
|
51
51
|
|
52
52
|
import logging
|
53
53
|
import threading
|
54
|
+
from collections.abc import Callable
|
54
55
|
from dataclasses import dataclass, field
|
55
|
-
from datetime import
|
56
|
+
from datetime import UTC, datetime
|
56
57
|
from enum import Enum
|
57
|
-
from typing import Any
|
58
|
+
from typing import Any
|
58
59
|
|
59
60
|
logger = logging.getLogger(__name__)
|
60
61
|
|
@@ -136,11 +137,11 @@ class UserContext:
|
|
136
137
|
user_id: str
|
137
138
|
tenant_id: str
|
138
139
|
email: str
|
139
|
-
roles:
|
140
|
-
permissions:
|
141
|
-
attributes:
|
142
|
-
session_id:
|
143
|
-
ip_address:
|
140
|
+
roles: list[str] = field(default_factory=list)
|
141
|
+
permissions: list[str] = field(default_factory=list)
|
142
|
+
attributes: dict[str, Any] = field(default_factory=dict) # Custom attributes
|
143
|
+
session_id: str | None = None
|
144
|
+
ip_address: str | None = None
|
144
145
|
|
145
146
|
|
146
147
|
@dataclass
|
@@ -195,21 +196,21 @@ class PermissionRule:
|
|
195
196
|
id: str
|
196
197
|
resource_type: str # "workflow" or "node"
|
197
198
|
resource_id: str # workflow_id or node_id
|
198
|
-
permission:
|
199
|
+
permission: WorkflowPermission | NodePermission
|
199
200
|
effect: PermissionEffect
|
200
201
|
|
201
202
|
# Who does this apply to?
|
202
|
-
user_id:
|
203
|
-
role:
|
204
|
-
tenant_id:
|
203
|
+
user_id: str | None = None # Specific user
|
204
|
+
role: str | None = None # Any user with role
|
205
|
+
tenant_id: str | None = None # All users in tenant
|
205
206
|
|
206
207
|
# Conditions
|
207
|
-
conditions:
|
208
|
+
conditions: dict[str, Any] = field(default_factory=dict)
|
208
209
|
|
209
210
|
# Metadata
|
210
|
-
created_at: datetime = field(default_factory=lambda: datetime.now(
|
211
|
-
created_by:
|
212
|
-
expires_at:
|
211
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
|
212
|
+
created_by: str | None = None
|
213
|
+
expires_at: datetime | None = None
|
213
214
|
priority: int = 0 # Higher priority rules evaluated first
|
214
215
|
|
215
216
|
|
@@ -262,17 +263,17 @@ class AccessDecision:
|
|
262
263
|
|
263
264
|
allowed: bool
|
264
265
|
reason: str
|
265
|
-
applied_rules:
|
266
|
-
conditions_met:
|
267
|
-
masked_fields:
|
268
|
-
redirect_node:
|
266
|
+
applied_rules: list[PermissionRule] = field(default_factory=list)
|
267
|
+
conditions_met: dict[str, bool] = field(default_factory=dict)
|
268
|
+
masked_fields: list[str] = field(default_factory=list) # Fields to mask in output
|
269
|
+
redirect_node: str | None = None # Alternative node to execute
|
269
270
|
|
270
271
|
|
271
272
|
class ConditionEvaluator:
|
272
273
|
"""Evaluates conditions for conditional permissions"""
|
273
274
|
|
274
275
|
def __init__(self):
|
275
|
-
self.evaluators:
|
276
|
+
self.evaluators: dict[str, Callable] = {
|
276
277
|
"time_range": self._eval_time_range,
|
277
278
|
"data_contains": self._eval_data_contains,
|
278
279
|
"user_attribute": self._eval_user_attribute,
|
@@ -281,7 +282,7 @@ class ConditionEvaluator:
|
|
281
282
|
}
|
282
283
|
|
283
284
|
def evaluate(
|
284
|
-
self, condition_type: str, condition_value: Any, context:
|
285
|
+
self, condition_type: str, condition_value: Any, context: dict[str, Any]
|
285
286
|
) -> bool:
|
286
287
|
"""Evaluate a condition"""
|
287
288
|
evaluator = self.evaluators.get(condition_type)
|
@@ -295,7 +296,7 @@ class ConditionEvaluator:
|
|
295
296
|
logger.error(f"Error evaluating condition {condition_type}: {e}")
|
296
297
|
return False
|
297
298
|
|
298
|
-
def _eval_time_range(self, value:
|
299
|
+
def _eval_time_range(self, value: dict[str, str], context: dict[str, Any]) -> bool:
|
299
300
|
"""Check if current time is within range"""
|
300
301
|
from datetime import datetime, time
|
301
302
|
|
@@ -305,7 +306,7 @@ class ConditionEvaluator:
|
|
305
306
|
return start <= now <= end
|
306
307
|
|
307
308
|
def _eval_data_contains(
|
308
|
-
self, value:
|
309
|
+
self, value: dict[str, Any], context: dict[str, Any]
|
309
310
|
) -> bool:
|
310
311
|
"""Check if data contains specific values"""
|
311
312
|
data = context.get("data", {})
|
@@ -317,7 +318,7 @@ class ConditionEvaluator:
|
|
317
318
|
return False
|
318
319
|
|
319
320
|
def _eval_user_attribute(
|
320
|
-
self, value:
|
321
|
+
self, value: dict[str, Any], context: dict[str, Any]
|
321
322
|
) -> bool:
|
322
323
|
"""Check user attributes"""
|
323
324
|
user = context.get("user")
|
@@ -329,7 +330,7 @@ class ConditionEvaluator:
|
|
329
330
|
|
330
331
|
return user.attributes.get(attr_name) == expected
|
331
332
|
|
332
|
-
def _eval_ip_range(self, value:
|
333
|
+
def _eval_ip_range(self, value: dict[str, Any], context: dict[str, Any]) -> bool:
|
333
334
|
"""Check if IP is in allowed range"""
|
334
335
|
# Simplified IP check - in production use ipaddress module
|
335
336
|
allowed_ips = value.get("allowed", [])
|
@@ -337,7 +338,7 @@ class ConditionEvaluator:
|
|
337
338
|
|
338
339
|
return user_ip in allowed_ips
|
339
340
|
|
340
|
-
def _eval_custom(self, value:
|
341
|
+
def _eval_custom(self, value: dict[str, Any], context: dict[str, Any]) -> bool:
|
341
342
|
"""Evaluate custom condition"""
|
342
343
|
# This would call a custom function registered by the user
|
343
344
|
return True
|
@@ -409,7 +410,7 @@ class AccessControlManager:
|
|
409
410
|
|
410
411
|
def __init__(self, enabled: bool = False):
|
411
412
|
self.enabled = enabled # Disabled by default
|
412
|
-
self.rules:
|
413
|
+
self.rules: list[PermissionRule] = []
|
413
414
|
self.condition_evaluator = ConditionEvaluator()
|
414
415
|
self._cache = {} # Cache access decisions
|
415
416
|
self._cache_lock = threading.Lock()
|
@@ -458,7 +459,7 @@ class AccessControlManager:
|
|
458
459
|
user: UserContext,
|
459
460
|
node_id: str,
|
460
461
|
permission: NodePermission,
|
461
|
-
runtime_context:
|
462
|
+
runtime_context: dict[str, Any] = None,
|
462
463
|
) -> AccessDecision:
|
463
464
|
"""Check if user has permission on node"""
|
464
465
|
cache_key = f"node:{node_id}:{user.user_id}:{permission.value}"
|
@@ -494,7 +495,7 @@ class AccessControlManager:
|
|
494
495
|
|
495
496
|
def get_accessible_nodes(
|
496
497
|
self, user: UserContext, workflow_id: str, permission: NodePermission
|
497
|
-
) ->
|
498
|
+
) -> set[str]:
|
498
499
|
"""Get all nodes user can access in a workflow"""
|
499
500
|
accessible = set()
|
500
501
|
|
@@ -518,9 +519,9 @@ class AccessControlManager:
|
|
518
519
|
self,
|
519
520
|
user: UserContext,
|
520
521
|
conditional_node_id: str,
|
521
|
-
true_path_nodes:
|
522
|
-
false_path_nodes:
|
523
|
-
) ->
|
522
|
+
true_path_nodes: list[str],
|
523
|
+
false_path_nodes: list[str],
|
524
|
+
) -> list[str]:
|
524
525
|
"""Determine which path user should take based on permissions"""
|
525
526
|
# Check if user has access to nodes in true path
|
526
527
|
true_path_accessible = all(
|
@@ -544,8 +545,8 @@ class AccessControlManager:
|
|
544
545
|
return []
|
545
546
|
|
546
547
|
def mask_node_output(
|
547
|
-
self, user: UserContext, node_id: str, output:
|
548
|
-
) ->
|
548
|
+
self, user: UserContext, node_id: str, output: dict[str, Any]
|
549
|
+
) -> dict[str, Any]:
|
549
550
|
"""Mask sensitive fields in node output"""
|
550
551
|
decision = self.check_node_access(user, node_id, NodePermission.READ_OUTPUT)
|
551
552
|
|
@@ -568,8 +569,8 @@ class AccessControlManager:
|
|
568
569
|
user: UserContext,
|
569
570
|
resource_type: str,
|
570
571
|
resource_id: str,
|
571
|
-
permission:
|
572
|
-
runtime_context:
|
572
|
+
permission: WorkflowPermission | NodePermission,
|
573
|
+
runtime_context: dict[str, Any],
|
573
574
|
) -> AccessDecision:
|
574
575
|
"""Evaluate all applicable rules"""
|
575
576
|
applicable_rules = []
|
@@ -584,7 +585,7 @@ class AccessControlManager:
|
|
584
585
|
):
|
585
586
|
|
586
587
|
# Check expiration
|
587
|
-
if rule.expires_at and rule.expires_at < datetime.now(
|
588
|
+
if rule.expires_at and rule.expires_at < datetime.now(UTC):
|
588
589
|
continue
|
589
590
|
|
590
591
|
applicable_rules.append(rule)
|
@@ -593,7 +594,7 @@ class AccessControlManager:
|
|
593
594
|
context = {
|
594
595
|
"user": user,
|
595
596
|
"runtime": runtime_context,
|
596
|
-
"timestamp": datetime.now(
|
597
|
+
"timestamp": datetime.now(UTC),
|
597
598
|
}
|
598
599
|
|
599
600
|
final_effect = PermissionEffect.DENY # Default deny
|
@@ -660,7 +661,7 @@ class AccessControlManager:
|
|
660
661
|
user: UserContext,
|
661
662
|
resource_type: str,
|
662
663
|
resource_id: str,
|
663
|
-
permission:
|
664
|
+
permission: WorkflowPermission | NodePermission,
|
664
665
|
decision: AccessDecision,
|
665
666
|
):
|
666
667
|
"""Log access attempt for audit"""
|
@@ -50,8 +50,8 @@ Future Enhancements:
|
|
50
50
|
import os
|
51
51
|
import secrets
|
52
52
|
import threading
|
53
|
-
from datetime import datetime, timedelta
|
54
|
-
from typing import Any
|
53
|
+
from datetime import UTC, datetime, timedelta
|
54
|
+
from typing import Any
|
55
55
|
|
56
56
|
from fastapi import Depends, HTTPException, Request, status
|
57
57
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
@@ -101,14 +101,14 @@ class User(Base):
|
|
101
101
|
|
102
102
|
# User roles and permissions
|
103
103
|
roles = Column(JSON, default=lambda: ["user"])
|
104
|
-
permissions = Column(JSON, default=
|
104
|
+
permissions = Column(JSON, default=list)
|
105
105
|
|
106
106
|
# Timestamps
|
107
|
-
created_at = Column(DateTime, default=lambda: datetime.now(
|
107
|
+
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
|
108
108
|
updated_at = Column(
|
109
109
|
DateTime,
|
110
|
-
default=lambda: datetime.now(
|
111
|
-
onupdate=lambda: datetime.now(
|
110
|
+
default=lambda: datetime.now(UTC),
|
111
|
+
onupdate=lambda: datetime.now(UTC),
|
112
112
|
)
|
113
113
|
last_login = Column(DateTime)
|
114
114
|
|
@@ -150,11 +150,11 @@ class Tenant(Base):
|
|
150
150
|
subscription_tier = Column(String(50), default="free")
|
151
151
|
|
152
152
|
# Timestamps
|
153
|
-
created_at = Column(DateTime, default=lambda: datetime.now(
|
153
|
+
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
|
154
154
|
updated_at = Column(
|
155
155
|
DateTime,
|
156
|
-
default=lambda: datetime.now(
|
157
|
-
onupdate=lambda: datetime.now(
|
156
|
+
default=lambda: datetime.now(UTC),
|
157
|
+
onupdate=lambda: datetime.now(UTC),
|
158
158
|
)
|
159
159
|
|
160
160
|
# Relationships
|
@@ -187,7 +187,7 @@ class APIKey(Base):
|
|
187
187
|
usage_count = Column(JSON, default=lambda: {"total": 0, "monthly": 0})
|
188
188
|
|
189
189
|
# Timestamps
|
190
|
-
created_at = Column(DateTime, default=lambda: datetime.now(
|
190
|
+
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
|
191
191
|
|
192
192
|
# Relationships
|
193
193
|
user = relationship("User", back_populates="api_keys")
|
@@ -203,7 +203,7 @@ class UserCreate(BaseModel):
|
|
203
203
|
email: EmailStr
|
204
204
|
username: str = Field(..., min_length=3, max_length=100)
|
205
205
|
password: str = Field(..., min_length=8)
|
206
|
-
tenant_id:
|
206
|
+
tenant_id: str | None = None # If None, create new tenant
|
207
207
|
|
208
208
|
|
209
209
|
class UserLogin(BaseModel):
|
@@ -227,9 +227,9 @@ class TokenData(BaseModel):
|
|
227
227
|
|
228
228
|
sub: str
|
229
229
|
tenant_id: str
|
230
|
-
roles:
|
231
|
-
permissions:
|
232
|
-
exp:
|
230
|
+
roles: list[str] = ["user"]
|
231
|
+
permissions: list[str] = []
|
232
|
+
exp: datetime | None = None
|
233
233
|
|
234
234
|
|
235
235
|
class JWTAuth:
|
@@ -240,33 +240,27 @@ class JWTAuth:
|
|
240
240
|
self.algorithm = ALGORITHM
|
241
241
|
|
242
242
|
def create_access_token(
|
243
|
-
self, data:
|
243
|
+
self, data: dict[str, Any], expires_delta: timedelta | None = None
|
244
244
|
) -> str:
|
245
245
|
"""Create a JWT access token"""
|
246
246
|
to_encode = data.copy()
|
247
247
|
|
248
248
|
if expires_delta:
|
249
|
-
expire = datetime.now(
|
249
|
+
expire = datetime.now(UTC) + expires_delta
|
250
250
|
else:
|
251
|
-
expire = datetime.now(
|
252
|
-
hours=ACCESS_TOKEN_EXPIRE_HOURS
|
253
|
-
)
|
251
|
+
expire = datetime.now(UTC) + timedelta(hours=ACCESS_TOKEN_EXPIRE_HOURS)
|
254
252
|
|
255
|
-
to_encode.update(
|
256
|
-
{"exp": expire, "iat": datetime.now(timezone.utc), "type": "access"}
|
257
|
-
)
|
253
|
+
to_encode.update({"exp": expire, "iat": datetime.now(UTC), "type": "access"})
|
258
254
|
|
259
255
|
encoded_jwt = jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm)
|
260
256
|
return encoded_jwt
|
261
257
|
|
262
|
-
def create_refresh_token(self, data:
|
258
|
+
def create_refresh_token(self, data: dict[str, Any]) -> str:
|
263
259
|
"""Create a JWT refresh token"""
|
264
260
|
to_encode = data.copy()
|
265
|
-
expire = datetime.now(
|
261
|
+
expire = datetime.now(UTC) + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
|
266
262
|
|
267
|
-
to_encode.update(
|
268
|
-
{"exp": expire, "iat": datetime.now(timezone.utc), "type": "refresh"}
|
269
|
-
)
|
263
|
+
to_encode.update({"exp": expire, "iat": datetime.now(UTC), "type": "refresh"})
|
270
264
|
|
271
265
|
encoded_jwt = jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm)
|
272
266
|
return encoded_jwt
|
@@ -421,13 +415,13 @@ async def verify_api_key(
|
|
421
415
|
)
|
422
416
|
|
423
417
|
# Check expiration
|
424
|
-
if valid_key.expires_at and valid_key.expires_at < datetime.now(
|
418
|
+
if valid_key.expires_at and valid_key.expires_at < datetime.now(UTC):
|
425
419
|
raise HTTPException(
|
426
420
|
status_code=status.HTTP_401_UNAUTHORIZED, detail="API key expired"
|
427
421
|
)
|
428
422
|
|
429
423
|
# Update usage
|
430
|
-
valid_key.last_used_at = datetime.now(
|
424
|
+
valid_key.last_used_at = datetime.now(UTC)
|
431
425
|
valid_key.usage_count["total"] += 1
|
432
426
|
valid_key.usage_count["monthly"] += 1
|
433
427
|
session.commit()
|
@@ -514,7 +508,7 @@ class TenantContext:
|
|
514
508
|
_tenant_context = threading.local()
|
515
509
|
|
516
510
|
|
517
|
-
def get_current_tenant_id() ->
|
511
|
+
def get_current_tenant_id() -> str | None:
|
518
512
|
"""Get current tenant ID from context"""
|
519
513
|
return getattr(_tenant_context, "tenant_id", None)
|
520
514
|
|
@@ -615,7 +609,7 @@ class AuthService:
|
|
615
609
|
)
|
616
610
|
|
617
611
|
# Update last login
|
618
|
-
user.last_login = datetime.now(
|
612
|
+
user.last_login = datetime.now(UTC)
|
619
613
|
self.session.commit()
|
620
614
|
|
621
615
|
# Generate tokens
|
@@ -646,7 +640,7 @@ class AuthService:
|
|
646
640
|
return self.auth.create_tokens(user)
|
647
641
|
|
648
642
|
def create_api_key(
|
649
|
-
self, name: str, user: User, scopes:
|
643
|
+
self, name: str, user: User, scopes: list[str] = None
|
650
644
|
) -> tuple[str, APIKey]:
|
651
645
|
"""Create an API key for a user"""
|
652
646
|
key, key_hash = create_api_key()
|