altcodepro-polydb-python 2.1.0__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {altcodepro_polydb_python-2.1.0/src/altcodepro_polydb_python.egg-info → altcodepro_polydb_python-2.2.0}/PKG-INFO +6 -5
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/README.md +3 -3
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/example_usage.py +6 -8
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/pyproject.toml +18 -35
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0/src/altcodepro_polydb_python.egg-info}/PKG-INFO +6 -5
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/altcodepro_polydb_python.egg-info/SOURCES.txt +1 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/altcodepro_polydb_python.egg-info/requires.txt +2 -1
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/__init__.py +25 -29
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/AzureQueueAdapter.py +3 -1
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/AzureTableStorageAdapter.py +2 -1
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/DynamoDBAdapter.py +1 -1
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/FirestoreAdapter.py +2 -1
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/PostgreSQLAdapter.py +127 -47
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/PubSubAdapter.py +3 -1
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/SQSAdapter.py +3 -1
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/VercelKVAdapter.py +4 -3
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/audit/models.py +3 -1
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/base/NoSQLKVAdapter.py +7 -5
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/cache.py +3 -2
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/databaseFactory.py +35 -9
- altcodepro_polydb_python-2.2.0/src/polydb/json_safe.py +8 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/multitenancy.py +2 -2
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/security.py +3 -1
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/LICENSE +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/MANIFEST.in +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/requirements-aws.txt +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/requirements-azure.txt +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/requirements-dev.txt +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/requirements-gcp.txt +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/requirements-generic.txt +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/requirements.txt +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/setup.cfg +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/setup.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/altcodepro_polydb_python.egg-info/dependency_links.txt +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/altcodepro_polydb_python.egg-info/top_level.txt +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/AzureBlobStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/AzureFileStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/EFSAdapter.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/GCPStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/MongoDBAdapter.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/S3Adapter.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/S3CompatibleAdapter.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/__init__.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/advanced_query.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/audit/AuditStorage.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/audit/__init__.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/audit/context.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/audit/manager.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/base/ObjectStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/base/QueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/base/SharedFilesAdapter.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/base/__init__.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/batch.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/decorators.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/errors.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/factory.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/models.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/monitoring.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/py.typed +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/query.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/registry.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/retry.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/schema.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/types.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/utils.py +0 -0
- {altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: altcodepro-polydb-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Production-ready multi-cloud database abstraction layer with connection pooling, retry logic, and thread safety
|
|
5
5
|
Author: AltCodePro
|
|
6
6
|
Project-URL: Homepage, https://github.com/altcodepro/polydb-python
|
|
@@ -25,6 +25,8 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Requires-Dist: psycopg2-binary>=2.9.11
|
|
27
27
|
Requires-Dist: tenacity>=9.1.4
|
|
28
|
+
Requires-Dist: redis>=6.4.0
|
|
29
|
+
Requires-Dist: python-dotenv>=1.1.1
|
|
28
30
|
Provides-Extra: aws
|
|
29
31
|
Requires-Dist: boto3>=1.42.47; extra == "aws"
|
|
30
32
|
Requires-Dist: botocore>=1.42.47; extra == "aws"
|
|
@@ -65,7 +67,6 @@ Requires-Dist: google-cloud-storage>=3.9.0; extra == "all"
|
|
|
65
67
|
Requires-Dist: pymongo>=4.16.0; extra == "all"
|
|
66
68
|
Requires-Dist: pika>=1.3.2; extra == "all"
|
|
67
69
|
Requires-Dist: requests>=2.32.5; extra == "all"
|
|
68
|
-
Requires-Dist: redis>=6.4.0; extra == "all"
|
|
69
70
|
Provides-Extra: dev
|
|
70
71
|
Requires-Dist: black>=26.1.0; extra == "dev"
|
|
71
72
|
Requires-Dist: flake8>=7.3.0; extra == "dev"
|
|
@@ -78,7 +79,7 @@ Requires-Dist: pytest-mock>=3.15.1; extra == "test"
|
|
|
78
79
|
Requires-Dist: moto>=5.1.21; extra == "test"
|
|
79
80
|
Dynamic: license-file
|
|
80
81
|
|
|
81
|
-
# PolyDB
|
|
82
|
+
# PolyDB v2.2.0 - Enterprise Database Abstraction Layer
|
|
82
83
|
|
|
83
84
|
**Production-ready, cloud-independent database abstraction with full LINQ support, field-level audit, cache, and overflow storage**
|
|
84
85
|
|
|
@@ -243,8 +244,8 @@ users = db.read(User, {"role": "admin"})
|
|
|
243
244
|
users = db.read(User, {"role": "admin"}, no_cache=True)
|
|
244
245
|
|
|
245
246
|
# Manual invalidation
|
|
246
|
-
from polydb.cache import
|
|
247
|
-
cache =
|
|
247
|
+
from polydb.cache import RedisCacheEngine
|
|
248
|
+
cache = RedisCacheEngine()
|
|
248
249
|
cache.invalidate("User")
|
|
249
250
|
cache.clear()
|
|
250
251
|
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PolyDB
|
|
1
|
+
# PolyDB v2.2.0 - Enterprise Database Abstraction Layer
|
|
2
2
|
|
|
3
3
|
**Production-ready, cloud-independent database abstraction with full LINQ support, field-level audit, cache, and overflow storage**
|
|
4
4
|
|
|
@@ -163,8 +163,8 @@ users = db.read(User, {"role": "admin"})
|
|
|
163
163
|
users = db.read(User, {"role": "admin"}, no_cache=True)
|
|
164
164
|
|
|
165
165
|
# Manual invalidation
|
|
166
|
-
from polydb.cache import
|
|
167
|
-
cache =
|
|
166
|
+
from polydb.cache import RedisCacheEngine
|
|
167
|
+
cache = RedisCacheEngine()
|
|
168
168
|
cache.invalidate("User")
|
|
169
169
|
cache.clear()
|
|
170
170
|
```
|
|
@@ -3,16 +3,14 @@
|
|
|
3
3
|
Complete PolyDB usage example with all features
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from polydb import (
|
|
7
|
-
DatabaseFactory,
|
|
8
|
-
polydb_model,
|
|
9
|
-
QueryBuilder,
|
|
10
|
-
Operator,
|
|
11
|
-
AuditContext,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
6
|
|
|
15
7
|
# 1. Define models
|
|
8
|
+
from polydb.audit.context import AuditContext
|
|
9
|
+
from polydb.databaseFactory import DatabaseFactory
|
|
10
|
+
from polydb.decorators import polydb_model
|
|
11
|
+
from polydb.query import Operator, QueryBuilder
|
|
12
|
+
|
|
13
|
+
|
|
16
14
|
@polydb_model
|
|
17
15
|
class User:
|
|
18
16
|
__polydb__ = {
|
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "altcodepro-polydb-python"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.2.0"
|
|
8
8
|
description = "Production-ready multi-cloud database abstraction layer with connection pooling, retry logic, and thread safety"
|
|
9
|
-
readme = {file = "README.md", content-type = "text/markdown"}
|
|
9
|
+
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.8"
|
|
11
11
|
license-files = ["LICENSE"]
|
|
12
|
-
authors = [{name = "AltCodePro"}]
|
|
12
|
+
authors = [{ name = "AltCodePro" }]
|
|
13
13
|
keywords = [
|
|
14
14
|
"database",
|
|
15
15
|
"cloud",
|
|
@@ -23,7 +23,7 @@ keywords = [
|
|
|
23
23
|
"postgres",
|
|
24
24
|
"mongodb",
|
|
25
25
|
"dynamodb",
|
|
26
|
-
"s3"
|
|
26
|
+
"s3",
|
|
27
27
|
]
|
|
28
28
|
classifiers = [
|
|
29
29
|
"Development Status :: 4 - Beta",
|
|
@@ -43,23 +43,22 @@ classifiers = [
|
|
|
43
43
|
# Core dependencies - always installed
|
|
44
44
|
dependencies = [
|
|
45
45
|
"psycopg2-binary>=2.9.11",
|
|
46
|
-
"tenacity>=9.1.4"
|
|
46
|
+
"tenacity>=9.1.4",
|
|
47
|
+
"redis>=6.4.0",
|
|
48
|
+
"python-dotenv>=1.1.1",
|
|
47
49
|
]
|
|
48
50
|
|
|
49
51
|
# Generic/Open-source stack (cheapest option)
|
|
50
52
|
[project.optional-dependencies]
|
|
51
53
|
|
|
52
|
-
aws = [
|
|
53
|
-
"boto3>=1.42.47",
|
|
54
|
-
"botocore>=1.42.47"
|
|
55
|
-
]
|
|
54
|
+
aws = ["boto3>=1.42.47", "botocore>=1.42.47"]
|
|
56
55
|
|
|
57
56
|
azure = [
|
|
58
57
|
"azure-core>=1.38.1",
|
|
59
58
|
"azure-data-tables>=12.7.0",
|
|
60
59
|
"azure-storage-blob>=12.28.0",
|
|
61
60
|
"azure-storage-file-share>=12.24.0",
|
|
62
|
-
"azure-storage-queue>=12.15.0"
|
|
61
|
+
"azure-storage-queue>=12.15.0",
|
|
63
62
|
]
|
|
64
63
|
|
|
65
64
|
gcp = [
|
|
@@ -68,26 +67,16 @@ gcp = [
|
|
|
68
67
|
"google-cloud-core>=2.5.0",
|
|
69
68
|
"google-cloud-firestore>=2.23.0",
|
|
70
69
|
"google-cloud-pubsub>=2.35.0",
|
|
71
|
-
"google-cloud-storage>=3.9.0"
|
|
70
|
+
"google-cloud-storage>=3.9.0",
|
|
72
71
|
]
|
|
73
72
|
|
|
74
|
-
mongodb = [
|
|
75
|
-
"pymongo>=4.16.0"
|
|
76
|
-
]
|
|
73
|
+
mongodb = ["pymongo>=4.16.0"]
|
|
77
74
|
|
|
78
|
-
rabbitmq = [
|
|
79
|
-
"pika>=1.3.2"
|
|
80
|
-
]
|
|
75
|
+
rabbitmq = ["pika>=1.3.2"]
|
|
81
76
|
|
|
82
|
-
vercel = [
|
|
83
|
-
"requests>=2.32.5"
|
|
84
|
-
]
|
|
77
|
+
vercel = ["requests>=2.32.5"]
|
|
85
78
|
|
|
86
|
-
generic = [
|
|
87
|
-
"pymongo>=4.16.0",
|
|
88
|
-
"pika>=1.3.2",
|
|
89
|
-
"boto3>=1.42.47"
|
|
90
|
-
]
|
|
79
|
+
generic = ["pymongo>=4.16.0", "pika>=1.3.2", "boto3>=1.42.47"]
|
|
91
80
|
|
|
92
81
|
all = [
|
|
93
82
|
"boto3>=1.42.47",
|
|
@@ -103,17 +92,11 @@ all = [
|
|
|
103
92
|
"pymongo>=4.16.0",
|
|
104
93
|
"pika>=1.3.2",
|
|
105
94
|
"requests>=2.32.5",
|
|
106
|
-
"redis>=6.4.0"
|
|
107
95
|
]
|
|
108
96
|
|
|
109
97
|
|
|
110
98
|
# Development dependencies
|
|
111
|
-
dev = [
|
|
112
|
-
"black>=26.1.0",
|
|
113
|
-
"flake8>=7.3.0",
|
|
114
|
-
"isort>=7.0.0",
|
|
115
|
-
"mypy>=1.19.1"
|
|
116
|
-
]
|
|
99
|
+
dev = ["black>=26.1.0", "flake8>=7.3.0", "isort>=7.0.0", "mypy>=1.19.1"]
|
|
117
100
|
|
|
118
101
|
|
|
119
102
|
# Testing with all providers
|
|
@@ -121,7 +104,7 @@ test = [
|
|
|
121
104
|
"pytest>=9.0.2",
|
|
122
105
|
"pytest-cov>=7.0.0",
|
|
123
106
|
"pytest-mock>=3.15.1",
|
|
124
|
-
"moto>=5.1.21"
|
|
107
|
+
"moto>=5.1.21",
|
|
125
108
|
]
|
|
126
109
|
|
|
127
110
|
[project.urls]
|
|
@@ -131,7 +114,7 @@ Repository = "https://github.com/altcodepro/polydb-python"
|
|
|
131
114
|
"Bug Tracker" = "https://github.com/altcodepro/polydb-python/issues"
|
|
132
115
|
|
|
133
116
|
[tool.setuptools]
|
|
134
|
-
package-dir = {"" = "src"}
|
|
117
|
+
package-dir = { "" = "src" }
|
|
135
118
|
|
|
136
119
|
[tool.setuptools.packages.find]
|
|
137
120
|
where = ["src"]
|
|
@@ -167,4 +150,4 @@ addopts = [
|
|
|
167
150
|
"--cov=polydb",
|
|
168
151
|
"--cov-report=term-missing",
|
|
169
152
|
"--cov-report=html",
|
|
170
|
-
]
|
|
153
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: altcodepro-polydb-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Production-ready multi-cloud database abstraction layer with connection pooling, retry logic, and thread safety
|
|
5
5
|
Author: AltCodePro
|
|
6
6
|
Project-URL: Homepage, https://github.com/altcodepro/polydb-python
|
|
@@ -25,6 +25,8 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Requires-Dist: psycopg2-binary>=2.9.11
|
|
27
27
|
Requires-Dist: tenacity>=9.1.4
|
|
28
|
+
Requires-Dist: redis>=6.4.0
|
|
29
|
+
Requires-Dist: python-dotenv>=1.1.1
|
|
28
30
|
Provides-Extra: aws
|
|
29
31
|
Requires-Dist: boto3>=1.42.47; extra == "aws"
|
|
30
32
|
Requires-Dist: botocore>=1.42.47; extra == "aws"
|
|
@@ -65,7 +67,6 @@ Requires-Dist: google-cloud-storage>=3.9.0; extra == "all"
|
|
|
65
67
|
Requires-Dist: pymongo>=4.16.0; extra == "all"
|
|
66
68
|
Requires-Dist: pika>=1.3.2; extra == "all"
|
|
67
69
|
Requires-Dist: requests>=2.32.5; extra == "all"
|
|
68
|
-
Requires-Dist: redis>=6.4.0; extra == "all"
|
|
69
70
|
Provides-Extra: dev
|
|
70
71
|
Requires-Dist: black>=26.1.0; extra == "dev"
|
|
71
72
|
Requires-Dist: flake8>=7.3.0; extra == "dev"
|
|
@@ -78,7 +79,7 @@ Requires-Dist: pytest-mock>=3.15.1; extra == "test"
|
|
|
78
79
|
Requires-Dist: moto>=5.1.21; extra == "test"
|
|
79
80
|
Dynamic: license-file
|
|
80
81
|
|
|
81
|
-
# PolyDB
|
|
82
|
+
# PolyDB v2.2.0 - Enterprise Database Abstraction Layer
|
|
82
83
|
|
|
83
84
|
**Production-ready, cloud-independent database abstraction with full LINQ support, field-level audit, cache, and overflow storage**
|
|
84
85
|
|
|
@@ -243,8 +244,8 @@ users = db.read(User, {"role": "admin"})
|
|
|
243
244
|
users = db.read(User, {"role": "admin"}, no_cache=True)
|
|
244
245
|
|
|
245
246
|
# Manual invalidation
|
|
246
|
-
from polydb.cache import
|
|
247
|
-
cache =
|
|
247
|
+
from polydb.cache import RedisCacheEngine
|
|
248
|
+
cache = RedisCacheEngine()
|
|
248
249
|
cache.invalidate("User")
|
|
249
250
|
cache.clear()
|
|
250
251
|
```
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
psycopg2-binary>=2.9.11
|
|
2
2
|
tenacity>=9.1.4
|
|
3
|
+
redis>=6.4.0
|
|
4
|
+
python-dotenv>=1.1.1
|
|
3
5
|
|
|
4
6
|
[all]
|
|
5
7
|
boto3>=1.42.47
|
|
@@ -15,7 +17,6 @@ google-cloud-storage>=3.9.0
|
|
|
15
17
|
pymongo>=4.16.0
|
|
16
18
|
pika>=1.3.2
|
|
17
19
|
requests>=2.32.5
|
|
18
|
-
redis>=6.4.0
|
|
19
20
|
|
|
20
21
|
[aws]
|
|
21
22
|
boto3>=1.42.47
|
|
@@ -4,7 +4,7 @@ PolyDB - Enterprise Cloud-Independent Database Abstraction
|
|
|
4
4
|
Full LINQ support, field-level audit, cache, soft delete, overflow storage
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "
|
|
7
|
+
__version__ = "2.2.0"
|
|
8
8
|
|
|
9
9
|
from .factory import CloudDatabaseFactory
|
|
10
10
|
from .databaseFactory import DatabaseFactory
|
|
@@ -12,7 +12,7 @@ from .models import CloudProvider, PartitionConfig
|
|
|
12
12
|
from .decorators import polydb_model
|
|
13
13
|
from .query import QueryBuilder, Operator
|
|
14
14
|
from .audit.context import AuditContext
|
|
15
|
-
from .cache import CacheEngine
|
|
15
|
+
from .cache import RedisCacheEngine as CacheEngine
|
|
16
16
|
from .errors import (
|
|
17
17
|
CloudDBError,
|
|
18
18
|
DatabaseError,
|
|
@@ -31,34 +31,30 @@ from .errors import (
|
|
|
31
31
|
|
|
32
32
|
__all__ = [
|
|
33
33
|
# Factories
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
"CloudDatabaseFactory",
|
|
35
|
+
"DatabaseFactory",
|
|
37
36
|
# Models & Config
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
"CloudProvider",
|
|
38
|
+
"PartitionConfig",
|
|
39
|
+
"polydb_model",
|
|
42
40
|
# Query
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
"QueryBuilder",
|
|
42
|
+
"Operator",
|
|
46
43
|
# Audit & Cache
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
"AuditContext",
|
|
45
|
+
"CacheEngine",
|
|
50
46
|
# Errors
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
]
|
|
47
|
+
"CloudDBError",
|
|
48
|
+
"DatabaseError",
|
|
49
|
+
"NoSQLError",
|
|
50
|
+
"StorageError",
|
|
51
|
+
"QueueError",
|
|
52
|
+
"ConnectionError",
|
|
53
|
+
"ValidationError",
|
|
54
|
+
"PolyDBError",
|
|
55
|
+
"ModelNotRegisteredError",
|
|
56
|
+
"InvalidModelMetadataError",
|
|
57
|
+
"UnsupportedStorageTypeError",
|
|
58
|
+
"AdapterConfigurationError",
|
|
59
|
+
"OperationNotSupportedError",
|
|
60
|
+
]
|
|
@@ -6,6 +6,8 @@ import os
|
|
|
6
6
|
import threading
|
|
7
7
|
from typing import Any, Dict, List
|
|
8
8
|
|
|
9
|
+
from src.polydb.json_safe import json_safe
|
|
10
|
+
|
|
9
11
|
class AzureQueueAdapter(QueueAdapter):
|
|
10
12
|
"""Azure Queue Storage with client reuse"""
|
|
11
13
|
|
|
@@ -36,7 +38,7 @@ class AzureQueueAdapter(QueueAdapter):
|
|
|
36
38
|
|
|
37
39
|
if self._client:
|
|
38
40
|
queue_client = self._client.get_queue_client(queue_name)
|
|
39
|
-
response = queue_client.send_message(json.dumps(message))
|
|
41
|
+
response = queue_client.send_message(json.dumps(message,default=json_safe))
|
|
40
42
|
return response.id
|
|
41
43
|
return ""
|
|
42
44
|
except Exception as e:
|
|
@@ -3,6 +3,7 @@ import os
|
|
|
3
3
|
import threading
|
|
4
4
|
from typing import Any, Dict, List, Optional
|
|
5
5
|
from polydb.base.NoSQLKVAdapter import NoSQLKVAdapter
|
|
6
|
+
from src.polydb.json_safe import json_safe
|
|
6
7
|
from ..errors import NoSQLError, ConnectionError
|
|
7
8
|
from ..retry import retry
|
|
8
9
|
from ..types import JsonDict
|
|
@@ -56,7 +57,7 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
56
57
|
data_copy['RowKey'] = rk
|
|
57
58
|
|
|
58
59
|
# Check size
|
|
59
|
-
data_bytes = json.dumps(data_copy).encode()
|
|
60
|
+
data_bytes = json.dumps(data_copy,default=json_safe).encode()
|
|
60
61
|
data_size = len(data_bytes)
|
|
61
62
|
|
|
62
63
|
if data_size > self.AZURE_TABLE_MAX_SIZE:
|
|
@@ -64,7 +64,7 @@ class DynamoDBAdapter(NoSQLKVAdapter):
|
|
|
64
64
|
data_copy['SK'] = rk
|
|
65
65
|
|
|
66
66
|
# Check size
|
|
67
|
-
data_bytes = json.dumps(data_copy).encode()
|
|
67
|
+
data_bytes = json.dumps(data_copy,default=json_safe).encode()
|
|
68
68
|
data_size = len(data_bytes)
|
|
69
69
|
|
|
70
70
|
if data_size > self.DYNAMODB_MAX_SIZE:
|
|
@@ -6,6 +6,7 @@ from google.cloud import firestore
|
|
|
6
6
|
from google.cloud import storage
|
|
7
7
|
from google.cloud.firestore import Client
|
|
8
8
|
from polydb.base.NoSQLKVAdapter import NoSQLKVAdapter
|
|
9
|
+
from src.polydb.json_safe import json_safe
|
|
9
10
|
|
|
10
11
|
from ..errors import NoSQLError, ConnectionError
|
|
11
12
|
from ..retry import retry
|
|
@@ -66,7 +67,7 @@ class FirestoreAdapter(NoSQLKVAdapter):
|
|
|
66
67
|
data_copy['_rk'] = rk
|
|
67
68
|
|
|
68
69
|
# Check size
|
|
69
|
-
data_bytes = json.dumps(data_copy).encode()
|
|
70
|
+
data_bytes = json.dumps(data_copy,default=json_safe).encode()
|
|
70
71
|
data_size = len(data_bytes)
|
|
71
72
|
|
|
72
73
|
if data_size > self.FIRESTORE_MAX_SIZE:
|
|
@@ -21,8 +21,10 @@ class PostgreSQLAdapter:
|
|
|
21
21
|
self.logger = setup_logger(__name__)
|
|
22
22
|
self.connection_string = os.getenv(
|
|
23
23
|
"POSTGRES_CONNECTION_STRING",
|
|
24
|
-
os.getenv("POSTGRES_URL", "
|
|
24
|
+
os.getenv("POSTGRES_URL", ""),
|
|
25
25
|
)
|
|
26
|
+
if not self.connection_string:
|
|
27
|
+
raise ConnectionError("POSTGRES_CONNECTION_STRING or POSTGRES_URL must be set")
|
|
26
28
|
self._pool = None
|
|
27
29
|
self._lock = threading.Lock()
|
|
28
30
|
self._initialize_pool()
|
|
@@ -42,24 +44,46 @@ class PostgreSQLAdapter:
|
|
|
42
44
|
except Exception as e:
|
|
43
45
|
raise ConnectionError(f"Failed to initialize PostgreSQL pool: {str(e)}")
|
|
44
46
|
|
|
45
|
-
def _get_connection(self):
|
|
47
|
+
def _get_connection(self) -> Any:
|
|
46
48
|
if not self._pool:
|
|
47
49
|
self._initialize_pool()
|
|
48
50
|
return self._pool.getconn() # type: ignore
|
|
49
51
|
|
|
50
|
-
def _return_connection(self, conn):
|
|
52
|
+
def _return_connection(self, conn: Any):
|
|
51
53
|
if self._pool and conn:
|
|
52
54
|
self._pool.putconn(conn)
|
|
53
55
|
|
|
56
|
+
def begin_transaction(self) -> Any:
|
|
57
|
+
"""Begin a transaction and return the connection handle."""
|
|
58
|
+
conn = self._get_connection()
|
|
59
|
+
conn.autocommit = False # Ensure transaction mode
|
|
60
|
+
return conn
|
|
61
|
+
|
|
62
|
+
def commit(self, tx: Any):
|
|
63
|
+
"""Commit the transaction using the provided connection."""
|
|
64
|
+
if tx:
|
|
65
|
+
tx.commit()
|
|
66
|
+
self._return_connection(tx)
|
|
67
|
+
|
|
68
|
+
def rollback(self, tx: Any):
|
|
69
|
+
"""Rollback the transaction using the provided connection."""
|
|
70
|
+
if tx:
|
|
71
|
+
tx.rollback()
|
|
72
|
+
self._return_connection(tx)
|
|
73
|
+
|
|
54
74
|
@retry(max_attempts=3, delay=1.0, exceptions=(DatabaseError,))
|
|
55
|
-
def insert(self, table: str, data: JsonDict) -> JsonDict:
|
|
75
|
+
def insert(self, table: str, data: JsonDict, tx: Optional[Any] = None) -> JsonDict:
|
|
56
76
|
table = validate_table_name(table)
|
|
57
77
|
for k in data.keys():
|
|
58
78
|
validate_column_name(k)
|
|
59
79
|
|
|
60
|
-
conn =
|
|
61
|
-
|
|
80
|
+
conn = tx
|
|
81
|
+
own_conn = False
|
|
82
|
+
if not conn:
|
|
62
83
|
conn = self._get_connection()
|
|
84
|
+
own_conn = True
|
|
85
|
+
|
|
86
|
+
try:
|
|
63
87
|
cursor = conn.cursor()
|
|
64
88
|
|
|
65
89
|
columns = ", ".join(data.keys())
|
|
@@ -71,15 +95,17 @@ class PostgreSQLAdapter:
|
|
|
71
95
|
columns_list = [desc[0] for desc in cursor.description]
|
|
72
96
|
result = dict(zip(columns_list, result_row))
|
|
73
97
|
|
|
74
|
-
|
|
98
|
+
if own_conn:
|
|
99
|
+
conn.commit()
|
|
100
|
+
|
|
75
101
|
cursor.close()
|
|
76
102
|
return result
|
|
77
103
|
except Exception as e:
|
|
78
|
-
if
|
|
104
|
+
if own_conn:
|
|
79
105
|
conn.rollback()
|
|
80
106
|
raise DatabaseError(f"Insert failed: {str(e)}")
|
|
81
107
|
finally:
|
|
82
|
-
if conn:
|
|
108
|
+
if own_conn and conn:
|
|
83
109
|
self._return_connection(conn)
|
|
84
110
|
|
|
85
111
|
@retry(max_attempts=3, delay=1.0, exceptions=(DatabaseError,))
|
|
@@ -89,12 +115,16 @@ class PostgreSQLAdapter:
|
|
|
89
115
|
query: Optional[Lookup] = None,
|
|
90
116
|
limit: Optional[int] = None,
|
|
91
117
|
offset: Optional[int] = None,
|
|
118
|
+
tx: Optional[Any] = None,
|
|
92
119
|
) -> List[JsonDict]:
|
|
93
120
|
table = validate_table_name(table)
|
|
94
|
-
conn =
|
|
121
|
+
conn = tx
|
|
122
|
+
own_conn = False
|
|
123
|
+
if not conn:
|
|
124
|
+
conn = self._get_connection()
|
|
125
|
+
own_conn = True
|
|
95
126
|
|
|
96
127
|
try:
|
|
97
|
-
conn = self._get_connection()
|
|
98
128
|
cursor = conn.cursor()
|
|
99
129
|
|
|
100
130
|
sql = f"SELECT * FROM {table}"
|
|
@@ -131,15 +161,20 @@ class PostgreSQLAdapter:
|
|
|
131
161
|
except Exception as e:
|
|
132
162
|
raise DatabaseError(f"Select failed: {str(e)}")
|
|
133
163
|
finally:
|
|
134
|
-
if conn:
|
|
164
|
+
if own_conn and conn:
|
|
135
165
|
self._return_connection(conn)
|
|
136
166
|
|
|
137
167
|
@retry(max_attempts=3, delay=1.0, exceptions=(DatabaseError,))
|
|
138
168
|
def select_page(
|
|
139
|
-
self,
|
|
169
|
+
self,
|
|
170
|
+
table: str,
|
|
171
|
+
query: Lookup,
|
|
172
|
+
page_size: int,
|
|
173
|
+
continuation_token: Optional[str] = None,
|
|
174
|
+
tx: Optional[Any] = None,
|
|
140
175
|
) -> Tuple[List[JsonDict], Optional[str]]:
|
|
141
176
|
offset = int(continuation_token) if continuation_token else 0
|
|
142
|
-
results = self.select(table, query, limit=page_size + 1, offset=offset)
|
|
177
|
+
results = self.select(table, query, limit=page_size + 1, offset=offset, tx=tx)
|
|
143
178
|
|
|
144
179
|
has_more = len(results) > page_size
|
|
145
180
|
if has_more:
|
|
@@ -149,14 +184,24 @@ class PostgreSQLAdapter:
|
|
|
149
184
|
return results, next_token
|
|
150
185
|
|
|
151
186
|
@retry(max_attempts=3, delay=1.0, exceptions=(DatabaseError,))
|
|
152
|
-
def update(
|
|
187
|
+
def update(
|
|
188
|
+
self,
|
|
189
|
+
table: str,
|
|
190
|
+
entity_id: Union[Any, Lookup],
|
|
191
|
+
data: JsonDict,
|
|
192
|
+
tx: Optional[Any] = None,
|
|
193
|
+
) -> JsonDict:
|
|
153
194
|
table = validate_table_name(table)
|
|
154
195
|
for k in data.keys():
|
|
155
196
|
validate_column_name(k)
|
|
156
197
|
|
|
157
|
-
conn =
|
|
158
|
-
|
|
198
|
+
conn = tx
|
|
199
|
+
own_conn = False
|
|
200
|
+
if not conn:
|
|
159
201
|
conn = self._get_connection()
|
|
202
|
+
own_conn = True
|
|
203
|
+
|
|
204
|
+
try:
|
|
160
205
|
cursor = conn.cursor()
|
|
161
206
|
|
|
162
207
|
set_clause = ", ".join([f"{k} = %s" for k in data.keys()])
|
|
@@ -183,26 +228,32 @@ class PostgreSQLAdapter:
|
|
|
183
228
|
columns = [desc[0] for desc in cursor.description]
|
|
184
229
|
result = dict(zip(columns, result_row))
|
|
185
230
|
|
|
186
|
-
|
|
231
|
+
if own_conn:
|
|
232
|
+
conn.commit()
|
|
233
|
+
|
|
187
234
|
cursor.close()
|
|
188
235
|
return result
|
|
189
236
|
except Exception as e:
|
|
190
|
-
if
|
|
237
|
+
if own_conn:
|
|
191
238
|
conn.rollback()
|
|
192
239
|
raise DatabaseError(f"Update failed: {str(e)}")
|
|
193
240
|
finally:
|
|
194
|
-
if conn:
|
|
241
|
+
if own_conn and conn:
|
|
195
242
|
self._return_connection(conn)
|
|
196
243
|
|
|
197
244
|
@retry(max_attempts=3, delay=1.0, exceptions=(DatabaseError,))
|
|
198
|
-
def upsert(self, table: str, data: JsonDict) -> JsonDict:
|
|
245
|
+
def upsert(self, table: str, data: JsonDict, tx: Optional[Any] = None) -> JsonDict:
|
|
199
246
|
table = validate_table_name(table)
|
|
200
247
|
for k in data.keys():
|
|
201
248
|
validate_column_name(k)
|
|
202
249
|
|
|
203
|
-
conn =
|
|
204
|
-
|
|
250
|
+
conn = tx
|
|
251
|
+
own_conn = False
|
|
252
|
+
if not conn:
|
|
205
253
|
conn = self._get_connection()
|
|
254
|
+
own_conn = True
|
|
255
|
+
|
|
256
|
+
try:
|
|
206
257
|
cursor = conn.cursor()
|
|
207
258
|
|
|
208
259
|
columns = ", ".join(data.keys())
|
|
@@ -226,24 +277,31 @@ class PostgreSQLAdapter:
|
|
|
226
277
|
columns_list = [desc[0] for desc in cursor.description]
|
|
227
278
|
result = dict(zip(columns_list, result_row))
|
|
228
279
|
|
|
229
|
-
|
|
280
|
+
if own_conn:
|
|
281
|
+
conn.commit()
|
|
282
|
+
|
|
230
283
|
cursor.close()
|
|
231
284
|
return result
|
|
232
285
|
except Exception as e:
|
|
233
|
-
if
|
|
286
|
+
if own_conn:
|
|
234
287
|
conn.rollback()
|
|
235
288
|
raise DatabaseError(f"Upsert failed: {str(e)}")
|
|
236
289
|
finally:
|
|
237
|
-
if conn:
|
|
290
|
+
if own_conn and conn:
|
|
238
291
|
self._return_connection(conn)
|
|
239
292
|
|
|
240
293
|
@retry(max_attempts=3, delay=1.0, exceptions=(DatabaseError,))
|
|
241
|
-
def delete(
|
|
294
|
+
def delete(
|
|
295
|
+
self, table: str, entity_id: Union[Any, Lookup], tx: Optional[Any] = None
|
|
296
|
+
) -> JsonDict:
|
|
242
297
|
table = validate_table_name(table)
|
|
243
|
-
conn =
|
|
298
|
+
conn = tx
|
|
299
|
+
own_conn = False
|
|
300
|
+
if not conn:
|
|
301
|
+
conn = self._get_connection()
|
|
302
|
+
own_conn = True
|
|
244
303
|
|
|
245
304
|
try:
|
|
246
|
-
conn = self._get_connection()
|
|
247
305
|
cursor = conn.cursor()
|
|
248
306
|
|
|
249
307
|
params = []
|
|
@@ -268,24 +326,31 @@ class PostgreSQLAdapter:
|
|
|
268
326
|
columns = [desc[0] for desc in cursor.description]
|
|
269
327
|
result = dict(zip(columns, result_row))
|
|
270
328
|
|
|
271
|
-
|
|
329
|
+
if own_conn:
|
|
330
|
+
conn.commit()
|
|
331
|
+
|
|
272
332
|
cursor.close()
|
|
273
333
|
return result
|
|
274
334
|
except Exception as e:
|
|
275
|
-
if
|
|
335
|
+
if own_conn:
|
|
276
336
|
conn.rollback()
|
|
277
337
|
raise DatabaseError(f"Delete failed: {str(e)}")
|
|
278
338
|
finally:
|
|
279
|
-
if conn:
|
|
339
|
+
if own_conn and conn:
|
|
280
340
|
self._return_connection(conn)
|
|
281
341
|
|
|
282
342
|
@retry(max_attempts=3, delay=1.0, exceptions=(DatabaseError,))
|
|
283
|
-
def query_linq(
|
|
343
|
+
def query_linq(
|
|
344
|
+
self, table: str, builder: QueryBuilder, tx: Optional[Any] = None
|
|
345
|
+
) -> Union[List[JsonDict], int]:
|
|
284
346
|
table = validate_table_name(table)
|
|
285
|
-
conn =
|
|
347
|
+
conn = tx
|
|
348
|
+
own_conn = False
|
|
349
|
+
if not conn:
|
|
350
|
+
conn = self._get_connection()
|
|
351
|
+
own_conn = True
|
|
286
352
|
|
|
287
353
|
try:
|
|
288
|
-
conn = self._get_connection()
|
|
289
354
|
cursor = conn.cursor()
|
|
290
355
|
|
|
291
356
|
if builder.count_only:
|
|
@@ -336,17 +401,24 @@ class PostgreSQLAdapter:
|
|
|
336
401
|
cursor.execute(sql, params)
|
|
337
402
|
|
|
338
403
|
if builder.count_only:
|
|
339
|
-
|
|
404
|
+
result = cursor.fetchone()[0]
|
|
405
|
+
else:
|
|
406
|
+
columns = [desc[0] for desc in cursor.description]
|
|
407
|
+
results = [dict(zip(columns, row)) for row in cursor.fetchall()]
|
|
408
|
+
result = results
|
|
340
409
|
|
|
341
|
-
columns = [desc[0] for desc in cursor.description]
|
|
342
|
-
results = [dict(zip(columns, row)) for row in cursor.fetchall()]
|
|
343
410
|
cursor.close()
|
|
344
411
|
|
|
345
|
-
|
|
412
|
+
if own_conn:
|
|
413
|
+
conn.commit()
|
|
414
|
+
|
|
415
|
+
return result
|
|
346
416
|
except Exception as e:
|
|
417
|
+
if own_conn:
|
|
418
|
+
conn.rollback()
|
|
347
419
|
raise DatabaseError(f"LINQ query failed: {str(e)}")
|
|
348
420
|
finally:
|
|
349
|
-
if conn:
|
|
421
|
+
if own_conn and conn:
|
|
350
422
|
self._return_connection(conn)
|
|
351
423
|
|
|
352
424
|
def __del__(self):
|
|
@@ -361,14 +433,19 @@ class PostgreSQLAdapter:
|
|
|
361
433
|
self,
|
|
362
434
|
sql: str,
|
|
363
435
|
params: Optional[List[Any]] = None,
|
|
436
|
+
tx: Optional[Any] = None,
|
|
364
437
|
*,
|
|
365
438
|
fetch: bool = False,
|
|
366
439
|
fetch_one: bool = False,
|
|
367
440
|
) -> Union[None, JsonDict, List[JsonDict]]:
|
|
368
|
-
conn =
|
|
441
|
+
conn = tx
|
|
442
|
+
own_conn = False
|
|
443
|
+
if not conn:
|
|
444
|
+
conn = self._get_connection()
|
|
445
|
+
own_conn = True
|
|
446
|
+
|
|
369
447
|
cursor = None
|
|
370
448
|
try:
|
|
371
|
-
conn = self._get_connection()
|
|
372
449
|
cursor = conn.cursor()
|
|
373
450
|
|
|
374
451
|
self.logger.debug("Executing raw SQL: %s", sql)
|
|
@@ -381,22 +458,25 @@ class PostgreSQLAdapter:
|
|
|
381
458
|
if row:
|
|
382
459
|
columns = [desc[0] for desc in cursor.description]
|
|
383
460
|
result = dict(zip(columns, row))
|
|
384
|
-
|
|
461
|
+
if own_conn:
|
|
462
|
+
conn.commit()
|
|
385
463
|
return result
|
|
386
464
|
|
|
387
465
|
if fetch:
|
|
388
466
|
rows = cursor.fetchall()
|
|
389
467
|
columns = [desc[0] for desc in cursor.description]
|
|
390
468
|
results = [dict(zip(columns, r)) for r in rows]
|
|
391
|
-
|
|
469
|
+
if own_conn:
|
|
470
|
+
conn.commit()
|
|
392
471
|
return results
|
|
393
472
|
|
|
394
473
|
# Non-fetch execution (DDL/DML)
|
|
395
|
-
|
|
474
|
+
if own_conn:
|
|
475
|
+
conn.commit()
|
|
396
476
|
return None
|
|
397
477
|
|
|
398
478
|
except Exception as e:
|
|
399
|
-
if
|
|
479
|
+
if own_conn:
|
|
400
480
|
try:
|
|
401
481
|
conn.rollback()
|
|
402
482
|
except Exception:
|
|
@@ -409,7 +489,7 @@ class PostgreSQLAdapter:
|
|
|
409
489
|
cursor.close()
|
|
410
490
|
except Exception:
|
|
411
491
|
pass
|
|
412
|
-
if conn:
|
|
492
|
+
if own_conn and conn:
|
|
413
493
|
self._return_connection(conn)
|
|
414
494
|
|
|
415
495
|
@contextmanager
|
|
@@ -7,6 +7,8 @@ import os
|
|
|
7
7
|
import threading
|
|
8
8
|
from typing import Any, Dict, List
|
|
9
9
|
|
|
10
|
+
from src.polydb.json_safe import json_safe
|
|
11
|
+
|
|
10
12
|
class PubSubAdapter(QueueAdapter):
|
|
11
13
|
"""GCP Pub/Sub with client reuse"""
|
|
12
14
|
|
|
@@ -42,7 +44,7 @@ class PubSubAdapter(QueueAdapter):
|
|
|
42
44
|
topic_path = self._publisher.topic_path(
|
|
43
45
|
self.project_id, queue_name or self.topic_name
|
|
44
46
|
)
|
|
45
|
-
data = json.dumps(message).encode("utf-8")
|
|
47
|
+
data = json.dumps(message,default=json_safe).encode("utf-8")
|
|
46
48
|
future = self._publisher.publish(topic_path, data)
|
|
47
49
|
return future.result()
|
|
48
50
|
return ""
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/SQSAdapter.py
RENAMED
|
@@ -12,6 +12,8 @@ import os
|
|
|
12
12
|
import threading
|
|
13
13
|
from typing import Any, Dict, List
|
|
14
14
|
|
|
15
|
+
from src.polydb.json_safe import json_safe
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
class SQSAdapter(QueueAdapter):
|
|
17
19
|
"""AWS SQS with client reuse"""
|
|
@@ -45,7 +47,7 @@ class SQSAdapter(QueueAdapter):
|
|
|
45
47
|
self._initialize_client()
|
|
46
48
|
if self._client:
|
|
47
49
|
response = self._client.send_message(
|
|
48
|
-
QueueUrl=self.queue_url, MessageBody=json.dumps(message)
|
|
50
|
+
QueueUrl=self.queue_url, MessageBody=json.dumps(message,default=json_safe)
|
|
49
51
|
)
|
|
50
52
|
return response["MessageId"]
|
|
51
53
|
return ""
|
|
@@ -3,6 +3,7 @@ import os
|
|
|
3
3
|
import threading
|
|
4
4
|
from typing import Any, Dict, List, Optional
|
|
5
5
|
from polydb.base.NoSQLKVAdapter import NoSQLKVAdapter
|
|
6
|
+
from src.polydb.json_safe import json_safe
|
|
6
7
|
from ..errors import NoSQLError, StorageError
|
|
7
8
|
from ..retry import retry
|
|
8
9
|
from ..types import JsonDict
|
|
@@ -36,7 +37,7 @@ class VercelKVAdapter(NoSQLKVAdapter):
|
|
|
36
37
|
data_copy['_rk'] = rk
|
|
37
38
|
|
|
38
39
|
# Check size
|
|
39
|
-
data_bytes = json.dumps(data_copy).encode()
|
|
40
|
+
data_bytes = json.dumps(data_copy,default=json_safe).encode()
|
|
40
41
|
data_size = len(data_bytes)
|
|
41
42
|
|
|
42
43
|
if data_size > self.VERCEL_KV_MAX_SIZE:
|
|
@@ -69,7 +70,7 @@ class VercelKVAdapter(NoSQLKVAdapter):
|
|
|
69
70
|
response = requests.post(
|
|
70
71
|
f"{self.kv_url}/set/{key}",
|
|
71
72
|
headers={'Authorization': f'Bearer {self.kv_token}'},
|
|
72
|
-
json={'value': json.dumps(reference_data)},
|
|
73
|
+
json={'value': json.dumps(reference_data,default=json_safe)},
|
|
73
74
|
timeout=self.timeout
|
|
74
75
|
)
|
|
75
76
|
response.raise_for_status()
|
|
@@ -78,7 +79,7 @@ class VercelKVAdapter(NoSQLKVAdapter):
|
|
|
78
79
|
response = requests.post(
|
|
79
80
|
f"{self.kv_url}/set/{key}",
|
|
80
81
|
headers={'Authorization': f'Bearer {self.kv_token}'},
|
|
81
|
-
json={'value': json.dumps(data_copy)},
|
|
82
|
+
json={'value': json.dumps(data_copy,default=json_safe)},
|
|
82
83
|
timeout=self.timeout
|
|
83
84
|
)
|
|
84
85
|
response.raise_for_status()
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/audit/models.py
RENAMED
|
@@ -7,6 +7,8 @@ import uuid
|
|
|
7
7
|
import hashlib
|
|
8
8
|
import json
|
|
9
9
|
|
|
10
|
+
from src.polydb.json_safe import json_safe
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
@dataclass
|
|
12
14
|
class AuditRecord:
|
|
@@ -80,7 +82,7 @@ class AuditRecord:
|
|
|
80
82
|
)
|
|
81
83
|
|
|
82
84
|
record.hash = hashlib.sha256(
|
|
83
|
-
json.dumps(asdict(record), sort_keys=True).encode()
|
|
85
|
+
json.dumps(asdict(record), sort_keys=True,default=json_safe).encode()
|
|
84
86
|
).hexdigest()
|
|
85
87
|
|
|
86
88
|
return record
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/base/NoSQLKVAdapter.py
RENAMED
|
@@ -6,6 +6,8 @@ import json
|
|
|
6
6
|
import threading
|
|
7
7
|
from typing import Any, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
|
|
8
8
|
|
|
9
|
+
from src.polydb.json_safe import json_safe
|
|
10
|
+
|
|
9
11
|
from ..errors import NoSQLError, StorageError
|
|
10
12
|
from ..retry import retry
|
|
11
13
|
from ..query import QueryBuilder, Operator
|
|
@@ -36,7 +38,7 @@ class NoSQLKVAdapter:
|
|
|
36
38
|
try:
|
|
37
39
|
pk = self.partition_config.partition_key_template.format(**data)
|
|
38
40
|
except KeyError:
|
|
39
|
-
pk = f"default_{data.get(pk_field, hashlib.md5(json.dumps(data, sort_keys=True).encode()).hexdigest()[:8])}"
|
|
41
|
+
pk = f"default_{data.get(pk_field, hashlib.md5(json.dumps(data, sort_keys=True,default=json_safe).encode()).hexdigest()[:8])}"
|
|
40
42
|
else:
|
|
41
43
|
pk = str(data.get(pk_field, 'default'))
|
|
42
44
|
|
|
@@ -46,16 +48,16 @@ class NoSQLKVAdapter:
|
|
|
46
48
|
try:
|
|
47
49
|
rk = self.partition_config.row_key_template.format(**data)
|
|
48
50
|
except KeyError:
|
|
49
|
-
rk = hashlib.md5(json.dumps(data, sort_keys=True).encode()).hexdigest()
|
|
51
|
+
rk = hashlib.md5(json.dumps(data, sort_keys=True,default=json_safe).encode()).hexdigest()
|
|
50
52
|
else:
|
|
51
|
-
rk = data.get('id', hashlib.md5(json.dumps(data, sort_keys=True).encode()).hexdigest())
|
|
53
|
+
rk = data.get('id', hashlib.md5(json.dumps(data, sort_keys=True,default=json_safe).encode()).hexdigest())
|
|
52
54
|
|
|
53
55
|
return str(pk), str(rk)
|
|
54
56
|
|
|
55
57
|
@retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
|
|
56
58
|
def _check_overflow(self, data: JsonDict) -> Tuple[JsonDict, Optional[str]]:
|
|
57
59
|
"""Check size and store in blob if needed"""
|
|
58
|
-
data_bytes = json.dumps(data).encode()
|
|
60
|
+
data_bytes = json.dumps(data,default=json_safe).encode()
|
|
59
61
|
data_size = len(data_bytes)
|
|
60
62
|
|
|
61
63
|
if data_size > self.max_size:
|
|
@@ -292,7 +294,7 @@ class NoSQLKVAdapter:
|
|
|
292
294
|
seen = set()
|
|
293
295
|
unique = []
|
|
294
296
|
for r in results:
|
|
295
|
-
key = json.dumps(r, sort_keys=True)
|
|
297
|
+
key = json.dumps(r, sort_keys=True,default=json_safe)
|
|
296
298
|
if key not in seen:
|
|
297
299
|
seen.add(key)
|
|
298
300
|
unique.append(r)
|
|
@@ -8,6 +8,7 @@ import hashlib
|
|
|
8
8
|
import threading
|
|
9
9
|
from enum import Enum
|
|
10
10
|
import redis
|
|
11
|
+
from src.polydb.json_safe import json_safe
|
|
11
12
|
|
|
12
13
|
class CacheStrategy(Enum):
|
|
13
14
|
"""Cache invalidation strategies"""
|
|
@@ -58,7 +59,7 @@ class RedisCacheEngine:
|
|
|
58
59
|
|
|
59
60
|
def _make_key(self, model: str, query: Dict[str, Any]) -> str:
|
|
60
61
|
"""Generate cache key"""
|
|
61
|
-
query_str = json.dumps(query, sort_keys=True)
|
|
62
|
+
query_str = json.dumps(query, sort_keys=True,default=json_safe)
|
|
62
63
|
query_hash = hashlib.md5(query_str.encode()).hexdigest()
|
|
63
64
|
return f"{self.prefix}{model}:{query_hash}"
|
|
64
65
|
|
|
@@ -94,7 +95,7 @@ class RedisCacheEngine:
|
|
|
94
95
|
ttl = ttl or self.default_ttl
|
|
95
96
|
|
|
96
97
|
try:
|
|
97
|
-
data = json.dumps(value)
|
|
98
|
+
data = json.dumps(value,default=json_safe)
|
|
98
99
|
self._client.setex(key, ttl, data)
|
|
99
100
|
|
|
100
101
|
# Initialize access count
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/databaseFactory.py
RENAMED
|
@@ -17,6 +17,7 @@ from .types import JsonDict, Lookup, ModelMeta
|
|
|
17
17
|
from .audit.manager import AuditManager
|
|
18
18
|
from .audit.context import AuditContext
|
|
19
19
|
from .query import Operator, QueryBuilder
|
|
20
|
+
from dotenv import load_dotenv
|
|
20
21
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
22
23
|
|
|
@@ -65,6 +66,11 @@ class DatabaseFactory:
|
|
|
65
66
|
# Redis cache (only if explicitly enabled + URL present)
|
|
66
67
|
self._cache: Optional[RedisCacheEngine] = None
|
|
67
68
|
self.cache_warmer: Optional[CacheWarmer] = None
|
|
69
|
+
try:
|
|
70
|
+
load_dotenv()
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
73
|
+
|
|
68
74
|
if enable_cache and use_redis_cache:
|
|
69
75
|
redis_url = os.getenv("REDIS_CACHE_URL")
|
|
70
76
|
if redis_url:
|
|
@@ -108,17 +114,28 @@ class DatabaseFactory:
|
|
|
108
114
|
return model.__name__ if isinstance(model, type) else str(model)
|
|
109
115
|
|
|
110
116
|
def _current_tenant_id(self) -> Optional[str]:
|
|
111
|
-
|
|
112
|
-
|
|
117
|
+
# Prefer TenantContext if present
|
|
118
|
+
try:
|
|
119
|
+
tenant = TenantContext.get_tenant()
|
|
120
|
+
if tenant and tenant.tenant_id:
|
|
121
|
+
return tenant.tenant_id
|
|
122
|
+
except Exception:
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
# Fallback to AuditContext
|
|
126
|
+
return AuditContext.tenant_id.get()
|
|
113
127
|
|
|
114
128
|
def _current_actor_id(self) -> Optional[str]:
|
|
115
129
|
return AuditContext.actor_id.get()
|
|
116
130
|
|
|
117
131
|
def _inject_tenant(self, data: JsonDict) -> JsonDict:
|
|
118
132
|
tenant_id = self._current_tenant_id()
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
133
|
+
|
|
134
|
+
if not tenant_id:
|
|
135
|
+
raise ValueError("Tenant ID is required but not set in AuditContext or TenantContext")
|
|
136
|
+
|
|
137
|
+
data = dict(data)
|
|
138
|
+
data.setdefault("tenant_id", tenant_id)
|
|
122
139
|
return data
|
|
123
140
|
|
|
124
141
|
def _inject_audit_fields(self, data: JsonDict, is_create: bool = False) -> JsonDict:
|
|
@@ -293,15 +310,17 @@ class DatabaseFactory:
|
|
|
293
310
|
meta = self._meta(model)
|
|
294
311
|
tenant_id = self._current_tenant_id()
|
|
295
312
|
actor_id = self._current_actor_id()
|
|
296
|
-
|
|
297
313
|
query = self._apply_soft_delete_filter(query if not include_deleted else None)
|
|
298
|
-
|
|
299
314
|
# Multi-tenancy & RLS filters
|
|
300
315
|
if self.tenant_enforcer:
|
|
301
316
|
query = self.tenant_enforcer.enforce_read(model_name, query or {})
|
|
302
317
|
if self.rls:
|
|
303
318
|
query = self.rls.enforce_read(model_name, query or {})
|
|
304
|
-
|
|
319
|
+
# Inject tenant filter (mandatory isolation)
|
|
320
|
+
tenant_id = self._current_tenant_id()
|
|
321
|
+
if tenant_id:
|
|
322
|
+
query = dict(query or {})
|
|
323
|
+
query.setdefault("tenant_id", tenant_id)
|
|
305
324
|
use_external_cache = self._enable_cache and self._cache and getattr(meta, "cache", False)
|
|
306
325
|
encrypted_fields = getattr(meta, "encrypted_fields", [])
|
|
307
326
|
|
|
@@ -408,7 +427,11 @@ class DatabaseFactory:
|
|
|
408
427
|
query = self.tenant_enforcer.enforce_read(model_name, query or {})
|
|
409
428
|
if self.rls:
|
|
410
429
|
query = self.rls.enforce_read(model_name, query or {})
|
|
411
|
-
|
|
430
|
+
# Inject tenant filter (mandatory isolation)
|
|
431
|
+
tenant_id = self._current_tenant_id()
|
|
432
|
+
if tenant_id:
|
|
433
|
+
query = dict(query or {})
|
|
434
|
+
query.setdefault("tenant_id", tenant_id)
|
|
412
435
|
encrypted_fields = getattr(meta, "encrypted_fields", [])
|
|
413
436
|
|
|
414
437
|
def _op() -> Tuple[List[JsonDict], Optional[str]]:
|
|
@@ -497,6 +520,9 @@ class DatabaseFactory:
|
|
|
497
520
|
nonlocal after_plain, success
|
|
498
521
|
|
|
499
522
|
if meta.storage == "sql" and meta.table:
|
|
523
|
+
if tenant_id:
|
|
524
|
+
if isinstance(entity_id, dict):
|
|
525
|
+
entity_id.setdefault("tenant_id", tenant_id)
|
|
500
526
|
result = self._sql.update(meta.table, entity_id, data)
|
|
501
527
|
else:
|
|
502
528
|
model_type = self._model_type(model)
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/multitenancy.py
RENAMED
|
@@ -4,7 +4,7 @@ Multi-tenancy enforcement and isolation
|
|
|
4
4
|
"""
|
|
5
5
|
from typing import Dict, Any, List, Optional, Callable
|
|
6
6
|
from contextvars import ContextVar
|
|
7
|
-
from dataclasses import dataclass
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
8
|
from enum import Enum
|
|
9
9
|
|
|
10
10
|
|
|
@@ -24,7 +24,7 @@ class TenantConfig:
|
|
|
24
24
|
database_name: Optional[str] = None
|
|
25
25
|
max_connections: int = 10
|
|
26
26
|
storage_quota_gb: Optional[float] = None
|
|
27
|
-
features: List[str] =
|
|
27
|
+
features: List[str] = field(default_factory=list)
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class TenantRegistry:
|
|
@@ -11,6 +11,8 @@ import json
|
|
|
11
11
|
from functools import wraps
|
|
12
12
|
import logging
|
|
13
13
|
|
|
14
|
+
from src.polydb.json_safe import json_safe
|
|
15
|
+
|
|
14
16
|
logger = logging.getLogger(__name__)
|
|
15
17
|
|
|
16
18
|
|
|
@@ -48,7 +50,7 @@ class FieldEncryption:
|
|
|
48
50
|
"""Encrypt arbitrary value (serialize if non-str)"""
|
|
49
51
|
if value is None:
|
|
50
52
|
return ""
|
|
51
|
-
data = json.dumps(value) if not isinstance(value, str) else value
|
|
53
|
+
data = json.dumps(value,default=json_safe) if not isinstance(value, str) else value
|
|
52
54
|
try:
|
|
53
55
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
54
56
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/EFSAdapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/S3Adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/adapters/__init__.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/advanced_query.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/audit/AuditStorage.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/audit/__init__.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/audit/context.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/audit/manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/base/QueueAdapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.1.0 → altcodepro_polydb_python-2.2.0}/src/polydb/base/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|