kailash 0.9.10__py3-none-any.whl → 0.9.12__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 +206 -19
- {kailash-0.9.10.dist-info → kailash-0.9.12.dist-info}/METADATA +9 -9
- {kailash-0.9.10.dist-info → kailash-0.9.12.dist-info}/RECORD +9 -9
- {kailash-0.9.10.dist-info → kailash-0.9.12.dist-info}/WHEEL +0 -0
- {kailash-0.9.10.dist-info → kailash-0.9.12.dist-info}/entry_points.txt +0 -0
- {kailash-0.9.10.dist-info → kailash-0.9.12.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.9.10.dist-info → kailash-0.9.12.dist-info}/licenses/NOTICE +0 -0
- {kailash-0.9.10.dist-info → kailash-0.9.12.dist-info}/top_level.txt +0 -0
kailash/__init__.py
CHANGED
kailash/nodes/data/async_sql.py
CHANGED
@@ -434,22 +434,63 @@ class PostgreSQLAdapter(DatabaseAdapter):
|
|
434
434
|
parameter_types: Optional[dict[str, str]] = None,
|
435
435
|
) -> Any:
|
436
436
|
"""Execute query and return results."""
|
437
|
+
|
438
|
+
import logging
|
439
|
+
|
440
|
+
logger = logging.getLogger(__name__)
|
441
|
+
|
437
442
|
# Convert dict params to positional for asyncpg
|
438
443
|
if isinstance(params, dict):
|
439
444
|
# Simple parameter substitution for named params
|
440
|
-
#
|
445
|
+
# Fix: Use proper ordering and safe replacement to avoid collisions
|
441
446
|
import json
|
447
|
+
import logging
|
448
|
+
|
449
|
+
logger = logging.getLogger(__name__)
|
442
450
|
|
443
451
|
query_params = []
|
444
452
|
param_names = [] # Track parameter names for type mapping
|
445
|
-
|
446
|
-
|
453
|
+
|
454
|
+
# Keep original order for parameter values, but do replacements safely
|
455
|
+
original_items = list(params.items())
|
456
|
+
|
457
|
+
# Create parameter values in original order
|
458
|
+
for key, value in original_items:
|
447
459
|
param_names.append(key)
|
448
460
|
# For PostgreSQL, lists should remain as lists for array operations
|
449
461
|
# Only convert dicts to JSON strings
|
450
462
|
if isinstance(value, dict):
|
451
463
|
value = json.dumps(value)
|
464
|
+
# Fix for parameter $11 issue: Handle ambiguous integer values
|
465
|
+
# AsyncPG has trouble with certain integer values (especially 0)
|
466
|
+
# where it can't determine the PostgreSQL type automatically
|
467
|
+
elif isinstance(value, int) and value == 0:
|
468
|
+
# Keep as int but we'll add explicit casting in the query later
|
469
|
+
pass
|
452
470
|
query_params.append(value)
|
471
|
+
|
472
|
+
# Replace parameters in the query, processing longer keys first to avoid collision
|
473
|
+
# Sort keys by length (descending) for replacement order only
|
474
|
+
keys_by_length = sorted(params.keys(), key=len, reverse=True)
|
475
|
+
|
476
|
+
for key in keys_by_length:
|
477
|
+
# Find the position of this key in the original order
|
478
|
+
position = (
|
479
|
+
next(i for i, (k, v) in enumerate(original_items) if k == key) + 1
|
480
|
+
)
|
481
|
+
value = original_items[position - 1][1] # Get the actual value
|
482
|
+
|
483
|
+
# Fix for parameter $11 issue: Add explicit type casting for ambiguous values
|
484
|
+
# Note: Check boolean first since bool is a subclass of int in Python
|
485
|
+
if isinstance(value, bool):
|
486
|
+
# Cast boolean parameters to avoid type ambiguity
|
487
|
+
query = query.replace(f":{key}", f"${position}::boolean")
|
488
|
+
elif isinstance(value, int):
|
489
|
+
# Cast integer parameters to avoid PostgreSQL type determination issues
|
490
|
+
query = query.replace(f":{key}", f"${position}::integer")
|
491
|
+
else:
|
492
|
+
query = query.replace(f":{key}", f"${position}")
|
493
|
+
|
453
494
|
params = query_params
|
454
495
|
|
455
496
|
# Apply parameter type casts if provided
|
@@ -462,6 +503,8 @@ class PostgreSQLAdapter(DatabaseAdapter):
|
|
462
503
|
query = query.replace(f"${i}", f"${i}::{pg_type}")
|
463
504
|
|
464
505
|
else:
|
506
|
+
# Parameters are not dict - they might be list/tuple
|
507
|
+
|
465
508
|
# For positional params, apply type casts if provided
|
466
509
|
if parameter_types and isinstance(params, (list, tuple)):
|
467
510
|
# Build query with type casts for positional parameters
|
@@ -671,9 +714,11 @@ class MySQLAdapter(DatabaseAdapter):
|
|
671
714
|
fetch_mode: FetchMode = FetchMode.ALL,
|
672
715
|
fetch_size: Optional[int] = None,
|
673
716
|
transaction: Optional[Any] = None,
|
717
|
+
parameter_types: Optional[dict[str, str]] = None,
|
674
718
|
) -> Any:
|
675
719
|
"""Execute query and return results."""
|
676
720
|
# Use transaction connection if provided, otherwise get from pool
|
721
|
+
# Note: parameter_types is only used by PostgreSQL adapter
|
677
722
|
if transaction:
|
678
723
|
conn = transaction
|
679
724
|
async with conn.cursor() as cursor:
|
@@ -774,6 +819,10 @@ class MySQLAdapter(DatabaseAdapter):
|
|
774
819
|
class SQLiteAdapter(DatabaseAdapter):
|
775
820
|
"""SQLite adapter using aiosqlite."""
|
776
821
|
|
822
|
+
# Class-level shared connections for memory databases to solve isolation issues
|
823
|
+
_shared_memory_connections = {}
|
824
|
+
_connection_locks = {}
|
825
|
+
|
777
826
|
async def connect(self) -> None:
|
778
827
|
"""Establish connection pool."""
|
779
828
|
try:
|
@@ -784,13 +833,71 @@ class SQLiteAdapter(DatabaseAdapter):
|
|
784
833
|
)
|
785
834
|
|
786
835
|
# SQLite doesn't have true connection pooling
|
787
|
-
# We'll manage
|
836
|
+
# We'll manage connections based on database type
|
788
837
|
self._aiosqlite = aiosqlite
|
789
|
-
|
838
|
+
|
839
|
+
# Extract database path from connection string if database path not provided
|
840
|
+
if self.config.database:
|
841
|
+
self._db_path = self.config.database
|
842
|
+
elif self.config.connection_string:
|
843
|
+
# Parse SQLite connection string formats:
|
844
|
+
# sqlite:///path/to/file.db (absolute path)
|
845
|
+
# sqlite://path/to/file.db (relative path - rare)
|
846
|
+
# file:path/to/file.db (file URI scheme)
|
847
|
+
conn_str = self.config.connection_string
|
848
|
+
if conn_str.startswith("sqlite:///"):
|
849
|
+
# Absolute path: sqlite:///path/to/file.db -> /path/to/file.db
|
850
|
+
# Special case: sqlite:///:memory: -> :memory:
|
851
|
+
path_part = conn_str[9:] # Remove "sqlite://" to keep the leading slash
|
852
|
+
if path_part == "/:memory:":
|
853
|
+
self._db_path = ":memory:"
|
854
|
+
else:
|
855
|
+
self._db_path = path_part
|
856
|
+
elif conn_str.startswith("sqlite://"):
|
857
|
+
# Relative path: sqlite://path/to/file.db -> path/to/file.db
|
858
|
+
self._db_path = conn_str[9:] # Remove "sqlite://"
|
859
|
+
elif conn_str.startswith("file:"):
|
860
|
+
# File URI: file:path/to/file.db -> path/to/file.db
|
861
|
+
self._db_path = conn_str[5:] # Remove "file:"
|
862
|
+
else:
|
863
|
+
# Assume the connection string IS the path
|
864
|
+
self._db_path = conn_str
|
865
|
+
else:
|
866
|
+
raise NodeExecutionError(
|
867
|
+
"SQLite requires either 'database' path or 'connection_string'"
|
868
|
+
)
|
869
|
+
|
870
|
+
# Set up connection sharing for memory databases to prevent isolation
|
871
|
+
self._is_memory_db = self._db_path == ":memory:"
|
872
|
+
if self._is_memory_db:
|
873
|
+
import asyncio
|
874
|
+
|
875
|
+
# All :memory: databases should share the same connection to avoid isolation
|
876
|
+
self._memory_key = "global_memory_db"
|
877
|
+
if self._memory_key not in self._connection_locks:
|
878
|
+
self._connection_locks[self._memory_key] = asyncio.Lock()
|
879
|
+
|
880
|
+
async def _get_connection(self):
|
881
|
+
"""Get a database connection, using shared connection for memory databases."""
|
882
|
+
if self._is_memory_db:
|
883
|
+
# Use shared connection for memory databases to prevent isolation
|
884
|
+
async with self._connection_locks[self._memory_key]:
|
885
|
+
if self._memory_key not in self._shared_memory_connections:
|
886
|
+
# Create the shared memory connection
|
887
|
+
conn = await self._aiosqlite.connect(self._db_path)
|
888
|
+
conn.row_factory = self._aiosqlite.Row
|
889
|
+
self._shared_memory_connections[self._memory_key] = conn
|
890
|
+
return self._shared_memory_connections[self._memory_key]
|
891
|
+
else:
|
892
|
+
# For file databases, create new connections as before
|
893
|
+
conn = await self._aiosqlite.connect(self._db_path)
|
894
|
+
conn.row_factory = self._aiosqlite.Row
|
895
|
+
return conn
|
790
896
|
|
791
897
|
async def disconnect(self) -> None:
|
792
898
|
"""Close connection."""
|
793
|
-
#
|
899
|
+
# For memory databases, we keep the shared connection alive
|
900
|
+
# For file databases, connections are managed per-operation
|
794
901
|
pass
|
795
902
|
|
796
903
|
async def execute(
|
@@ -800,6 +907,7 @@ class SQLiteAdapter(DatabaseAdapter):
|
|
800
907
|
fetch_mode: FetchMode = FetchMode.ALL,
|
801
908
|
fetch_size: Optional[int] = None,
|
802
909
|
transaction: Optional[Any] = None,
|
910
|
+
parameter_types: Optional[dict[str, str]] = None,
|
803
911
|
) -> Any:
|
804
912
|
"""Execute query and return results."""
|
805
913
|
if transaction:
|
@@ -820,21 +928,43 @@ class SQLiteAdapter(DatabaseAdapter):
|
|
820
928
|
return [self._convert_row(dict(row)) for row in rows]
|
821
929
|
else:
|
822
930
|
# Create new connection for non-transactional queries
|
823
|
-
|
824
|
-
|
931
|
+
if self._is_memory_db:
|
932
|
+
# Use shared connection for memory databases
|
933
|
+
db = await self._get_connection()
|
825
934
|
cursor = await db.execute(query, params or [])
|
826
935
|
|
827
936
|
if fetch_mode == FetchMode.ONE:
|
828
937
|
row = await cursor.fetchone()
|
829
|
-
|
938
|
+
result = self._convert_row(dict(row)) if row else None
|
830
939
|
elif fetch_mode == FetchMode.ALL:
|
831
940
|
rows = await cursor.fetchall()
|
832
|
-
|
941
|
+
result = [self._convert_row(dict(row)) for row in rows]
|
833
942
|
elif fetch_mode == FetchMode.MANY:
|
834
943
|
if not fetch_size:
|
835
944
|
raise ValueError("fetch_size required for MANY mode")
|
836
945
|
rows = await cursor.fetchmany(fetch_size)
|
837
|
-
|
946
|
+
result = [self._convert_row(dict(row)) for row in rows]
|
947
|
+
|
948
|
+
# Commit for memory databases (needed for INSERT/UPDATE/DELETE)
|
949
|
+
await db.commit()
|
950
|
+
return result
|
951
|
+
else:
|
952
|
+
# Use context manager for file databases
|
953
|
+
async with self._aiosqlite.connect(self._db_path) as db:
|
954
|
+
db.row_factory = self._aiosqlite.Row
|
955
|
+
cursor = await db.execute(query, params or [])
|
956
|
+
|
957
|
+
if fetch_mode == FetchMode.ONE:
|
958
|
+
row = await cursor.fetchone()
|
959
|
+
return self._convert_row(dict(row)) if row else None
|
960
|
+
elif fetch_mode == FetchMode.ALL:
|
961
|
+
rows = await cursor.fetchall()
|
962
|
+
return [self._convert_row(dict(row)) for row in rows]
|
963
|
+
elif fetch_mode == FetchMode.MANY:
|
964
|
+
if not fetch_size:
|
965
|
+
raise ValueError("fetch_size required for MANY mode")
|
966
|
+
rows = await cursor.fetchmany(fetch_size)
|
967
|
+
return [self._convert_row(dict(row)) for row in rows]
|
838
968
|
|
839
969
|
await db.commit()
|
840
970
|
|
@@ -851,26 +981,44 @@ class SQLiteAdapter(DatabaseAdapter):
|
|
851
981
|
# Don't commit here - let transaction handling do it
|
852
982
|
else:
|
853
983
|
# Create new connection for non-transactional queries
|
854
|
-
|
984
|
+
if self._is_memory_db:
|
985
|
+
# Use shared connection for memory databases
|
986
|
+
db = await self._get_connection()
|
855
987
|
await db.executemany(query, params_list)
|
856
988
|
await db.commit()
|
989
|
+
else:
|
990
|
+
# Use context manager for file databases
|
991
|
+
async with self._aiosqlite.connect(self._db_path) as db:
|
992
|
+
await db.executemany(query, params_list)
|
993
|
+
await db.commit()
|
857
994
|
|
858
995
|
async def begin_transaction(self) -> Any:
|
859
996
|
"""Begin a transaction."""
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
997
|
+
if self._is_memory_db:
|
998
|
+
# Use shared connection for memory databases
|
999
|
+
db = await self._get_connection()
|
1000
|
+
await db.execute("BEGIN")
|
1001
|
+
return db
|
1002
|
+
else:
|
1003
|
+
# Create new connection for file databases
|
1004
|
+
db = await self._aiosqlite.connect(self._db_path)
|
1005
|
+
db.row_factory = self._aiosqlite.Row
|
1006
|
+
await db.execute("BEGIN")
|
1007
|
+
return db
|
864
1008
|
|
865
1009
|
async def commit_transaction(self, transaction: Any) -> None:
|
866
1010
|
"""Commit a transaction."""
|
867
1011
|
await transaction.commit()
|
868
|
-
|
1012
|
+
# Don't close shared memory connections
|
1013
|
+
if not self._is_memory_db:
|
1014
|
+
await transaction.close()
|
869
1015
|
|
870
1016
|
async def rollback_transaction(self, transaction: Any) -> None:
|
871
1017
|
"""Rollback a transaction."""
|
872
1018
|
await transaction.rollback()
|
873
|
-
|
1019
|
+
# Don't close shared memory connections
|
1020
|
+
if not self._is_memory_db:
|
1021
|
+
await transaction.close()
|
874
1022
|
|
875
1023
|
|
876
1024
|
class DatabaseConfigManager:
|
@@ -1421,8 +1569,44 @@ class AsyncSQLDatabaseNode(AsyncNode):
|
|
1421
1569
|
# Re-initialize instance variables with updated config
|
1422
1570
|
self._reinitialize_from_config()
|
1423
1571
|
|
1424
|
-
#
|
1572
|
+
# Auto-detect database type from connection string if not explicitly set
|
1425
1573
|
db_type = self.config.get("database_type", "").lower()
|
1574
|
+
connection_string = self.config.get("connection_string")
|
1575
|
+
|
1576
|
+
# If database_type is the default and we have a connection string, try to auto-detect
|
1577
|
+
if (
|
1578
|
+
db_type == "postgresql"
|
1579
|
+
and connection_string
|
1580
|
+
and self.config.get("database_type")
|
1581
|
+
== self.get_parameters()["database_type"].default
|
1582
|
+
):
|
1583
|
+
try:
|
1584
|
+
# Simple detection based on connection string patterns
|
1585
|
+
conn_lower = connection_string.lower()
|
1586
|
+
if (
|
1587
|
+
connection_string == ":memory:"
|
1588
|
+
or conn_lower.endswith(".db")
|
1589
|
+
or conn_lower.endswith(".sqlite")
|
1590
|
+
or conn_lower.endswith(".sqlite3")
|
1591
|
+
or conn_lower.startswith("sqlite")
|
1592
|
+
or
|
1593
|
+
# File path without URL scheme (likely SQLite)
|
1594
|
+
("/" in connection_string and "://" not in connection_string)
|
1595
|
+
):
|
1596
|
+
db_type = "sqlite"
|
1597
|
+
self.config["database_type"] = "sqlite"
|
1598
|
+
elif conn_lower.startswith("mysql"):
|
1599
|
+
db_type = "mysql"
|
1600
|
+
self.config["database_type"] = "mysql"
|
1601
|
+
elif conn_lower.startswith(("postgresql", "postgres")):
|
1602
|
+
db_type = "postgresql"
|
1603
|
+
self.config["database_type"] = "postgresql"
|
1604
|
+
# Otherwise keep default postgresql
|
1605
|
+
except Exception:
|
1606
|
+
# If detection fails, keep the default
|
1607
|
+
pass
|
1608
|
+
|
1609
|
+
# Validate database type
|
1426
1610
|
if db_type not in ["postgresql", "mysql", "sqlite"]:
|
1427
1611
|
raise NodeValidationError(
|
1428
1612
|
f"Invalid database_type: {db_type}. "
|
@@ -1908,6 +2092,9 @@ class AsyncSQLDatabaseNode(AsyncNode):
|
|
1908
2092
|
except Exception as e:
|
1909
2093
|
last_error = e
|
1910
2094
|
|
2095
|
+
# Parameter type determination is now handled during dict-to-positional conversion
|
2096
|
+
# No special retry logic needed for parameter $11
|
2097
|
+
|
1911
2098
|
# Check if error is retryable
|
1912
2099
|
if not self._retry_config.should_retry(e):
|
1913
2100
|
raise
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: kailash
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.12
|
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=rLudbyeIwYeEEJKd5sX5BAGJfIJULcTGe0hvtbVdP3c,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=lVNbYaKnanK4Ejm6VZSPSUuPqBItlP7rAQ3Zvp-KeYg,114285
|
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.12.dist-info/licenses/LICENSE,sha256=9GYZHXVUmx6FdFRNzOeE_w7a_aEGeYbqTVmFtJlrbGk,13438
|
407
|
+
kailash-0.9.12.dist-info/licenses/NOTICE,sha256=9ssIK4LcHSTFqriXGdteMpBPTS1rSLlYtjppZ_bsjZ0,723
|
408
|
+
kailash-0.9.12.dist-info/METADATA,sha256=BeJ1vff7OZ9KO4Vw2_vDpXjCWBDonz30uREPxYa78pw,23528
|
409
|
+
kailash-0.9.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
410
|
+
kailash-0.9.12.dist-info/entry_points.txt,sha256=M_q3b8PG5W4XbhSgESzIJjh3_4OBKtZFYFsOdkr2vO4,45
|
411
|
+
kailash-0.9.12.dist-info/top_level.txt,sha256=z7GzH2mxl66498pVf5HKwo5wwfPtt9Aq95uZUpH6JV0,8
|
412
|
+
kailash-0.9.12.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|