kailash 0.9.10__py3-none-any.whl → 0.9.11__py3-none-any.whl
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/__init__.py +1 -1
- kailash/nodes/data/async_sql.py +157 -16
- {kailash-0.9.10.dist-info → kailash-0.9.11.dist-info}/METADATA +9 -9
- {kailash-0.9.10.dist-info → kailash-0.9.11.dist-info}/RECORD +9 -9
- {kailash-0.9.10.dist-info → kailash-0.9.11.dist-info}/WHEEL +0 -0
- {kailash-0.9.10.dist-info → kailash-0.9.11.dist-info}/entry_points.txt +0 -0
- {kailash-0.9.10.dist-info → kailash-0.9.11.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.9.10.dist-info → kailash-0.9.11.dist-info}/licenses/NOTICE +0 -0
- {kailash-0.9.10.dist-info → kailash-0.9.11.dist-info}/top_level.txt +0 -0
kailash/__init__.py
CHANGED
kailash/nodes/data/async_sql.py
CHANGED
@@ -671,9 +671,11 @@ class MySQLAdapter(DatabaseAdapter):
|
|
671
671
|
fetch_mode: FetchMode = FetchMode.ALL,
|
672
672
|
fetch_size: Optional[int] = None,
|
673
673
|
transaction: Optional[Any] = None,
|
674
|
+
parameter_types: Optional[dict[str, str]] = None,
|
674
675
|
) -> Any:
|
675
676
|
"""Execute query and return results."""
|
676
677
|
# Use transaction connection if provided, otherwise get from pool
|
678
|
+
# Note: parameter_types is only used by PostgreSQL adapter
|
677
679
|
if transaction:
|
678
680
|
conn = transaction
|
679
681
|
async with conn.cursor() as cursor:
|
@@ -774,6 +776,10 @@ class MySQLAdapter(DatabaseAdapter):
|
|
774
776
|
class SQLiteAdapter(DatabaseAdapter):
|
775
777
|
"""SQLite adapter using aiosqlite."""
|
776
778
|
|
779
|
+
# Class-level shared connections for memory databases to solve isolation issues
|
780
|
+
_shared_memory_connections = {}
|
781
|
+
_connection_locks = {}
|
782
|
+
|
777
783
|
async def connect(self) -> None:
|
778
784
|
"""Establish connection pool."""
|
779
785
|
try:
|
@@ -784,13 +790,71 @@ class SQLiteAdapter(DatabaseAdapter):
|
|
784
790
|
)
|
785
791
|
|
786
792
|
# SQLite doesn't have true connection pooling
|
787
|
-
# We'll manage
|
793
|
+
# We'll manage connections based on database type
|
788
794
|
self._aiosqlite = aiosqlite
|
789
|
-
|
795
|
+
|
796
|
+
# Extract database path from connection string if database path not provided
|
797
|
+
if self.config.database:
|
798
|
+
self._db_path = self.config.database
|
799
|
+
elif self.config.connection_string:
|
800
|
+
# Parse SQLite connection string formats:
|
801
|
+
# sqlite:///path/to/file.db (absolute path)
|
802
|
+
# sqlite://path/to/file.db (relative path - rare)
|
803
|
+
# file:path/to/file.db (file URI scheme)
|
804
|
+
conn_str = self.config.connection_string
|
805
|
+
if conn_str.startswith("sqlite:///"):
|
806
|
+
# Absolute path: sqlite:///path/to/file.db -> /path/to/file.db
|
807
|
+
# Special case: sqlite:///:memory: -> :memory:
|
808
|
+
path_part = conn_str[9:] # Remove "sqlite://" to keep the leading slash
|
809
|
+
if path_part == "/:memory:":
|
810
|
+
self._db_path = ":memory:"
|
811
|
+
else:
|
812
|
+
self._db_path = path_part
|
813
|
+
elif conn_str.startswith("sqlite://"):
|
814
|
+
# Relative path: sqlite://path/to/file.db -> path/to/file.db
|
815
|
+
self._db_path = conn_str[9:] # Remove "sqlite://"
|
816
|
+
elif conn_str.startswith("file:"):
|
817
|
+
# File URI: file:path/to/file.db -> path/to/file.db
|
818
|
+
self._db_path = conn_str[5:] # Remove "file:"
|
819
|
+
else:
|
820
|
+
# Assume the connection string IS the path
|
821
|
+
self._db_path = conn_str
|
822
|
+
else:
|
823
|
+
raise NodeExecutionError(
|
824
|
+
"SQLite requires either 'database' path or 'connection_string'"
|
825
|
+
)
|
826
|
+
|
827
|
+
# Set up connection sharing for memory databases to prevent isolation
|
828
|
+
self._is_memory_db = self._db_path == ":memory:"
|
829
|
+
if self._is_memory_db:
|
830
|
+
import asyncio
|
831
|
+
|
832
|
+
# All :memory: databases should share the same connection to avoid isolation
|
833
|
+
self._memory_key = "global_memory_db"
|
834
|
+
if self._memory_key not in self._connection_locks:
|
835
|
+
self._connection_locks[self._memory_key] = asyncio.Lock()
|
836
|
+
|
837
|
+
async def _get_connection(self):
|
838
|
+
"""Get a database connection, using shared connection for memory databases."""
|
839
|
+
if self._is_memory_db:
|
840
|
+
# Use shared connection for memory databases to prevent isolation
|
841
|
+
async with self._connection_locks[self._memory_key]:
|
842
|
+
if self._memory_key not in self._shared_memory_connections:
|
843
|
+
# Create the shared memory connection
|
844
|
+
conn = await self._aiosqlite.connect(self._db_path)
|
845
|
+
conn.row_factory = self._aiosqlite.Row
|
846
|
+
self._shared_memory_connections[self._memory_key] = conn
|
847
|
+
return self._shared_memory_connections[self._memory_key]
|
848
|
+
else:
|
849
|
+
# For file databases, create new connections as before
|
850
|
+
conn = await self._aiosqlite.connect(self._db_path)
|
851
|
+
conn.row_factory = self._aiosqlite.Row
|
852
|
+
return conn
|
790
853
|
|
791
854
|
async def disconnect(self) -> None:
|
792
855
|
"""Close connection."""
|
793
|
-
#
|
856
|
+
# For memory databases, we keep the shared connection alive
|
857
|
+
# For file databases, connections are managed per-operation
|
794
858
|
pass
|
795
859
|
|
796
860
|
async def execute(
|
@@ -800,6 +864,7 @@ class SQLiteAdapter(DatabaseAdapter):
|
|
800
864
|
fetch_mode: FetchMode = FetchMode.ALL,
|
801
865
|
fetch_size: Optional[int] = None,
|
802
866
|
transaction: Optional[Any] = None,
|
867
|
+
parameter_types: Optional[dict[str, str]] = None,
|
803
868
|
) -> Any:
|
804
869
|
"""Execute query and return results."""
|
805
870
|
if transaction:
|
@@ -820,21 +885,43 @@ class SQLiteAdapter(DatabaseAdapter):
|
|
820
885
|
return [self._convert_row(dict(row)) for row in rows]
|
821
886
|
else:
|
822
887
|
# Create new connection for non-transactional queries
|
823
|
-
|
824
|
-
|
888
|
+
if self._is_memory_db:
|
889
|
+
# Use shared connection for memory databases
|
890
|
+
db = await self._get_connection()
|
825
891
|
cursor = await db.execute(query, params or [])
|
826
892
|
|
827
893
|
if fetch_mode == FetchMode.ONE:
|
828
894
|
row = await cursor.fetchone()
|
829
|
-
|
895
|
+
result = self._convert_row(dict(row)) if row else None
|
830
896
|
elif fetch_mode == FetchMode.ALL:
|
831
897
|
rows = await cursor.fetchall()
|
832
|
-
|
898
|
+
result = [self._convert_row(dict(row)) for row in rows]
|
833
899
|
elif fetch_mode == FetchMode.MANY:
|
834
900
|
if not fetch_size:
|
835
901
|
raise ValueError("fetch_size required for MANY mode")
|
836
902
|
rows = await cursor.fetchmany(fetch_size)
|
837
|
-
|
903
|
+
result = [self._convert_row(dict(row)) for row in rows]
|
904
|
+
|
905
|
+
# Commit for memory databases (needed for INSERT/UPDATE/DELETE)
|
906
|
+
await db.commit()
|
907
|
+
return result
|
908
|
+
else:
|
909
|
+
# Use context manager for file databases
|
910
|
+
async with self._aiosqlite.connect(self._db_path) as db:
|
911
|
+
db.row_factory = self._aiosqlite.Row
|
912
|
+
cursor = await db.execute(query, params or [])
|
913
|
+
|
914
|
+
if fetch_mode == FetchMode.ONE:
|
915
|
+
row = await cursor.fetchone()
|
916
|
+
return self._convert_row(dict(row)) if row else None
|
917
|
+
elif fetch_mode == FetchMode.ALL:
|
918
|
+
rows = await cursor.fetchall()
|
919
|
+
return [self._convert_row(dict(row)) for row in rows]
|
920
|
+
elif fetch_mode == FetchMode.MANY:
|
921
|
+
if not fetch_size:
|
922
|
+
raise ValueError("fetch_size required for MANY mode")
|
923
|
+
rows = await cursor.fetchmany(fetch_size)
|
924
|
+
return [self._convert_row(dict(row)) for row in rows]
|
838
925
|
|
839
926
|
await db.commit()
|
840
927
|
|
@@ -851,26 +938,44 @@ class SQLiteAdapter(DatabaseAdapter):
|
|
851
938
|
# Don't commit here - let transaction handling do it
|
852
939
|
else:
|
853
940
|
# Create new connection for non-transactional queries
|
854
|
-
|
941
|
+
if self._is_memory_db:
|
942
|
+
# Use shared connection for memory databases
|
943
|
+
db = await self._get_connection()
|
855
944
|
await db.executemany(query, params_list)
|
856
945
|
await db.commit()
|
946
|
+
else:
|
947
|
+
# Use context manager for file databases
|
948
|
+
async with self._aiosqlite.connect(self._db_path) as db:
|
949
|
+
await db.executemany(query, params_list)
|
950
|
+
await db.commit()
|
857
951
|
|
858
952
|
async def begin_transaction(self) -> Any:
|
859
953
|
"""Begin a transaction."""
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
954
|
+
if self._is_memory_db:
|
955
|
+
# Use shared connection for memory databases
|
956
|
+
db = await self._get_connection()
|
957
|
+
await db.execute("BEGIN")
|
958
|
+
return db
|
959
|
+
else:
|
960
|
+
# Create new connection for file databases
|
961
|
+
db = await self._aiosqlite.connect(self._db_path)
|
962
|
+
db.row_factory = self._aiosqlite.Row
|
963
|
+
await db.execute("BEGIN")
|
964
|
+
return db
|
864
965
|
|
865
966
|
async def commit_transaction(self, transaction: Any) -> None:
|
866
967
|
"""Commit a transaction."""
|
867
968
|
await transaction.commit()
|
868
|
-
|
969
|
+
# Don't close shared memory connections
|
970
|
+
if not self._is_memory_db:
|
971
|
+
await transaction.close()
|
869
972
|
|
870
973
|
async def rollback_transaction(self, transaction: Any) -> None:
|
871
974
|
"""Rollback a transaction."""
|
872
975
|
await transaction.rollback()
|
873
|
-
|
976
|
+
# Don't close shared memory connections
|
977
|
+
if not self._is_memory_db:
|
978
|
+
await transaction.close()
|
874
979
|
|
875
980
|
|
876
981
|
class DatabaseConfigManager:
|
@@ -1421,8 +1526,44 @@ class AsyncSQLDatabaseNode(AsyncNode):
|
|
1421
1526
|
# Re-initialize instance variables with updated config
|
1422
1527
|
self._reinitialize_from_config()
|
1423
1528
|
|
1424
|
-
#
|
1529
|
+
# Auto-detect database type from connection string if not explicitly set
|
1425
1530
|
db_type = self.config.get("database_type", "").lower()
|
1531
|
+
connection_string = self.config.get("connection_string")
|
1532
|
+
|
1533
|
+
# If database_type is the default and we have a connection string, try to auto-detect
|
1534
|
+
if (
|
1535
|
+
db_type == "postgresql"
|
1536
|
+
and connection_string
|
1537
|
+
and self.config.get("database_type")
|
1538
|
+
== self.get_parameters()["database_type"].default
|
1539
|
+
):
|
1540
|
+
try:
|
1541
|
+
# Simple detection based on connection string patterns
|
1542
|
+
conn_lower = connection_string.lower()
|
1543
|
+
if (
|
1544
|
+
connection_string == ":memory:"
|
1545
|
+
or conn_lower.endswith(".db")
|
1546
|
+
or conn_lower.endswith(".sqlite")
|
1547
|
+
or conn_lower.endswith(".sqlite3")
|
1548
|
+
or conn_lower.startswith("sqlite")
|
1549
|
+
or
|
1550
|
+
# File path without URL scheme (likely SQLite)
|
1551
|
+
("/" in connection_string and "://" not in connection_string)
|
1552
|
+
):
|
1553
|
+
db_type = "sqlite"
|
1554
|
+
self.config["database_type"] = "sqlite"
|
1555
|
+
elif conn_lower.startswith("mysql"):
|
1556
|
+
db_type = "mysql"
|
1557
|
+
self.config["database_type"] = "mysql"
|
1558
|
+
elif conn_lower.startswith(("postgresql", "postgres")):
|
1559
|
+
db_type = "postgresql"
|
1560
|
+
self.config["database_type"] = "postgresql"
|
1561
|
+
# Otherwise keep default postgresql
|
1562
|
+
except Exception:
|
1563
|
+
# If detection fails, keep the default
|
1564
|
+
pass
|
1565
|
+
|
1566
|
+
# Validate database type
|
1426
1567
|
if db_type not in ["postgresql", "mysql", "sqlite"]:
|
1427
1568
|
raise NodeValidationError(
|
1428
1569
|
f"Invalid database_type: {db_type}. "
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: kailash
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.11
|
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
|
@@ -105,7 +105,7 @@ Dynamic: requires-python
|
|
105
105
|
<a href="https://pepy.tech/project/kailash"><img src="https://static.pepy.tech/badge/kailash" alt="Downloads"></a>
|
106
106
|
<img src="https://img.shields.io/badge/license-Apache%202.0%20with%20Additional%20Terms-orange.svg" alt="Apache 2.0 with Additional Terms">
|
107
107
|
<img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code style: black">
|
108
|
-
<img src="https://img.shields.io/badge/tests-
|
108
|
+
<img src="https://img.shields.io/badge/tests-4000%2B%20passing-brightgreen.svg" alt="Tests: 4000+ Passing">
|
109
109
|
<img src="https://img.shields.io/badge/performance-11x%20faster-yellow.svg" alt="Performance: 11x Faster">
|
110
110
|
<img src="https://img.shields.io/badge/docker-integrated-blue.svg" alt="Docker: Integrated">
|
111
111
|
<img src="https://img.shields.io/badge/AI-MCP%20validated-purple.svg" alt="AI: MCP Validated">
|
@@ -158,7 +158,7 @@ Not just a toolkit - complete production-ready applications built on enterprise-
|
|
158
158
|
- **11x faster test execution** (117s → 10.75s) with smart isolation
|
159
159
|
- **31.8M operations/second** query performance baseline
|
160
160
|
- **30,000+ iterations/second** cyclic workflow execution
|
161
|
-
- **100% test pass rate** across
|
161
|
+
- **100% test pass rate** across 4,000+ tests
|
162
162
|
|
163
163
|
### 🤖 **AI-First Architecture**
|
164
164
|
- **A2A Google Protocol** for enterprise multi-agent coordination
|
@@ -198,7 +198,7 @@ kailash_python_sdk/
|
|
198
198
|
│ ├── kailash-mcp/ # Enterprise MCP platform
|
199
199
|
│ ├── ai_registry/ # Advanced RAG capabilities
|
200
200
|
│ └── user_management/ # Enterprise RBAC system
|
201
|
-
├── tests/ #
|
201
|
+
├── tests/ # 4,000+ tests (100% pass rate)
|
202
202
|
├── docs/ # Comprehensive documentation
|
203
203
|
└── examples/ # Feature validation examples
|
204
204
|
```
|
@@ -299,7 +299,7 @@ results, run_id = runtime.execute(workflow.build())
|
|
299
299
|
## 🎯 Key Features
|
300
300
|
|
301
301
|
### 🧪 **Testing Excellence**
|
302
|
-
- **
|
302
|
+
- **4,000+ tests** with 100% pass rate
|
303
303
|
- **11x performance improvement** (117s → 10.75s execution)
|
304
304
|
- **Docker integration** for real PostgreSQL, Redis, MongoDB
|
305
305
|
- **Smart isolation** without process forking overhead
|
@@ -347,7 +347,7 @@ results, run_id = runtime.execute(workflow.build())
|
|
347
347
|
|
348
348
|
### Recent Achievements
|
349
349
|
- **11x faster test execution**: 117s → 10.75s with smart isolation
|
350
|
-
- **100% test pass rate**:
|
350
|
+
- **100% test pass rate**: 4,000+ tests across all categories
|
351
351
|
- **31.8M operations/second**: Query performance baseline
|
352
352
|
- **30,000+ iterations/second**: Cyclic workflow execution
|
353
353
|
|
@@ -401,7 +401,7 @@ pip install kailash-user-management
|
|
401
401
|
|
402
402
|
### Comprehensive Test Suite
|
403
403
|
```bash
|
404
|
-
# All tests (
|
404
|
+
# All tests (4,000+ tests)
|
405
405
|
pytest
|
406
406
|
|
407
407
|
# Fast unit tests (11x faster execution)
|
@@ -496,7 +496,7 @@ git clone https://github.com/integrum/kailash-python-sdk.git
|
|
496
496
|
cd kailash-python-sdk
|
497
497
|
uv sync
|
498
498
|
|
499
|
-
# Run tests (
|
499
|
+
# Run tests (4,000+ tests)
|
500
500
|
pytest tests/unit/ --timeout=1 # Fast unit tests
|
501
501
|
pytest tests/integration/ --timeout=5 # Integration tests
|
502
502
|
pytest tests/e2e/ --timeout=10 # End-to-end tests
|
@@ -529,7 +529,7 @@ See [Contributing Guide](CONTRIBUTING.md) and [sdk-contributors/CLAUDE.md](sdk-c
|
|
529
529
|
- **Complete Application Framework**: DataFlow, Nexus, AI Registry, User Management
|
530
530
|
- **PyPI Integration**: All packages available with proper extras support
|
531
531
|
- **Performance Breakthrough**: 11x faster test execution
|
532
|
-
- **Testing Excellence**:
|
532
|
+
- **Testing Excellence**: 4,000+ tests with 100% pass rate
|
533
533
|
- **Enterprise Ready**: Production deployment patterns
|
534
534
|
|
535
535
|
### ✅ v0.7.0 - Major Framework Release
|
@@ -1,4 +1,4 @@
|
|
1
|
-
kailash/__init__.py,sha256=
|
1
|
+
kailash/__init__.py,sha256=Nm6AU7y6IWWdXr9CuNwAH60wuVULzQpPUy5cLLTPgQ4,2772
|
2
2
|
kailash/__main__.py,sha256=vr7TVE5o16V6LsTmRFKG6RDKUXHpIWYdZ6Dok2HkHnI,198
|
3
3
|
kailash/access_control.py,sha256=MjKtkoQ2sg1Mgfe7ovGxVwhAbpJKvaepPWr8dxOueMA,26058
|
4
4
|
kailash/access_control_abac.py,sha256=FPfa_8PuDP3AxTjdWfiH3ntwWO8NodA0py9W8SE5dno,30263
|
@@ -203,7 +203,7 @@ kailash/nodes/compliance/data_retention.py,sha256=90bH_eGwlcDzUdklAJeXQM-RcuLUGQ
|
|
203
203
|
kailash/nodes/compliance/gdpr.py,sha256=ZMoHZjAo4QtGwtFCzGMrAUBFV3TbZOnJ5DZGZS87Bas,70548
|
204
204
|
kailash/nodes/data/__init__.py,sha256=f0h4ysvXxlyFcNJLvDyXrgJ0ixwDF1cS0pJ2QNPakhg,5213
|
205
205
|
kailash/nodes/data/async_connection.py,sha256=wfArHs9svU48bxGZIiixSV2YVn9cukNgEjagwTRu6J4,17250
|
206
|
-
kailash/nodes/data/async_sql.py,sha256=
|
206
|
+
kailash/nodes/data/async_sql.py,sha256=EPyWTP9B1ws9mCGdcJYU37of-qBklEuXlAMHwJFoQMU,112110
|
207
207
|
kailash/nodes/data/async_vector.py,sha256=HtwQLO25IXu8Vq80qzU8rMkUAKPQ2qM0x8YxjXHlygU,21005
|
208
208
|
kailash/nodes/data/bulk_operations.py,sha256=WVopmosVkIlweFxVt3boLdCPc93EqpYyQ1Ez9mCIt0c,34453
|
209
209
|
kailash/nodes/data/directory.py,sha256=fbfLqD_ijRubk-4xew3604QntPsyDxqaF4k6TpfyjDg,9923
|
@@ -403,10 +403,10 @@ kailash/workflow/templates.py,sha256=XQMAKZXC2dlxgMMQhSEOWAF3hIbe9JJt9j_THchhAm8
|
|
403
403
|
kailash/workflow/type_inference.py,sha256=i1F7Yd_Z3elTXrthsLpqGbOnQBIVVVEjhRpI0HrIjd0,24492
|
404
404
|
kailash/workflow/validation.py,sha256=r2zApGiiG8UEn7p5Ji842l8OR1_KftzDkWc7gg0cac0,44675
|
405
405
|
kailash/workflow/visualization.py,sha256=nHBW-Ai8QBMZtn2Nf3EE1_aiMGi9S6Ui_BfpA5KbJPU,23187
|
406
|
-
kailash-0.9.
|
407
|
-
kailash-0.9.
|
408
|
-
kailash-0.9.
|
409
|
-
kailash-0.9.
|
410
|
-
kailash-0.9.
|
411
|
-
kailash-0.9.
|
412
|
-
kailash-0.9.
|
406
|
+
kailash-0.9.11.dist-info/licenses/LICENSE,sha256=9GYZHXVUmx6FdFRNzOeE_w7a_aEGeYbqTVmFtJlrbGk,13438
|
407
|
+
kailash-0.9.11.dist-info/licenses/NOTICE,sha256=9ssIK4LcHSTFqriXGdteMpBPTS1rSLlYtjppZ_bsjZ0,723
|
408
|
+
kailash-0.9.11.dist-info/METADATA,sha256=mVyKebe-SSC9dsVrf3tIJCEVbq2Oz--W9AA59itYRP0,23528
|
409
|
+
kailash-0.9.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
410
|
+
kailash-0.9.11.dist-info/entry_points.txt,sha256=M_q3b8PG5W4XbhSgESzIJjh3_4OBKtZFYFsOdkr2vO4,45
|
411
|
+
kailash-0.9.11.dist-info/top_level.txt,sha256=z7GzH2mxl66498pVf5HKwo5wwfPtt9Aq95uZUpH6JV0,8
|
412
|
+
kailash-0.9.11.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|