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 CHANGED
@@ -50,7 +50,7 @@ except ImportError:
50
50
  # For backward compatibility
51
51
  WorkflowGraph = Workflow
52
52
 
53
- __version__ = "0.9.10"
53
+ __version__ = "0.9.11"
54
54
 
55
55
  __all__ = [
56
56
  # Core workflow components
@@ -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 a single connection for simplicity
793
+ # We'll manage connections based on database type
788
794
  self._aiosqlite = aiosqlite
789
- self._db_path = self.config.database
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
- # Connections are managed per-operation for SQLite
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
- async with self._aiosqlite.connect(self._db_path) as db:
824
- db.row_factory = self._aiosqlite.Row
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
- return self._convert_row(dict(row)) if row else None
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
- return [self._convert_row(dict(row)) for row in rows]
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
- return [self._convert_row(dict(row)) for row in rows]
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
- async with self._aiosqlite.connect(self._db_path) as db:
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
- db = await self._aiosqlite.connect(self._db_path)
861
- db.row_factory = self._aiosqlite.Row
862
- await db.execute("BEGIN")
863
- return db
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
- await transaction.close()
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
- await transaction.close()
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
- # Validate database type
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.10
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-2400%2B%20passing-brightgreen.svg" alt="Tests: 2400+ Passing">
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 2,400+ tests
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/ # 2,400+ tests (100% pass rate)
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
- - **2,400+ tests** with 100% pass rate
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**: 2,400+ tests across all categories
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 (2,400+ 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 (2,400+ 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**: 2,400+ tests with 100% pass rate
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=amrc4JD2yQaPHlYDEMvn1L-jk22-FDF8AZmh8ND_35I,2772
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=yKwX4_gFc_Qi6sCF134XCgtERyIhAZKK7DomYSZG3bo,105201
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.10.dist-info/licenses/LICENSE,sha256=9GYZHXVUmx6FdFRNzOeE_w7a_aEGeYbqTVmFtJlrbGk,13438
407
- kailash-0.9.10.dist-info/licenses/NOTICE,sha256=9ssIK4LcHSTFqriXGdteMpBPTS1rSLlYtjppZ_bsjZ0,723
408
- kailash-0.9.10.dist-info/METADATA,sha256=uU7Mhecq6GvLVX5nPxKHbwxMFY_IdrV5IqdGWyRvBLA,23528
409
- kailash-0.9.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
410
- kailash-0.9.10.dist-info/entry_points.txt,sha256=M_q3b8PG5W4XbhSgESzIJjh3_4OBKtZFYFsOdkr2vO4,45
411
- kailash-0.9.10.dist-info/top_level.txt,sha256=z7GzH2mxl66498pVf5HKwo5wwfPtt9Aq95uZUpH6JV0,8
412
- kailash-0.9.10.dist-info/RECORD,,
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,,