altcodepro-polydb-python 2.5.9__py3-none-any.whl → 2.5.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.
- {altcodepro_polydb_python-2.5.9.dist-info → altcodepro_polydb_python-2.5.11.dist-info}/METADATA +1 -1
- altcodepro_polydb_python-2.5.11.dist-info/RECORD +74 -0
- polydb/PolyDB.py +1 -1
- polydb/__init__.py +25 -14
- polydb/adapters/AzureBlobStorageAdapter.py +2 -1
- polydb/adapters/AzureQueueAdapter.py +3 -5
- polydb/adapters/AzureTableStorageAdapter.py +27 -9
- polydb/adapters/BlockchainBlobAdapter.py +0 -1
- polydb/adapters/BlockchainFileAdapter.py +1 -1
- polydb/adapters/BlockchainKVAdapter.py +20 -5
- polydb/adapters/BlockchainQueueAdapter.py +0 -1
- polydb/adapters/DynamoDBAdapter.py +2 -6
- polydb/adapters/EFSAdapter.py +1 -1
- polydb/adapters/GCPFilestoreAdapter.py +1 -1
- polydb/adapters/GCPPubSubAdapter.py +2 -2
- polydb/adapters/GCPStorageAdapter.py +1 -1
- polydb/adapters/KafkaQueueAdapter.py +10 -16
- polydb/adapters/MongoDBAdapter.py +3 -5
- polydb/adapters/PostgreSQLAdapter.py +38 -13
- polydb/adapters/RabbitMQAdapter.py +2 -2
- polydb/adapters/S3Adapter.py +1 -3
- polydb/adapters/S3CompatibleAdapter.py +1 -1
- polydb/adapters/SQSAdapter.py +1 -2
- polydb/adapters/VercelBlobAdapter.py +1 -1
- polydb/adapters/VercelKVAdapter.py +6 -5
- polydb/adapters/VercelQueueAdapter.py +18 -4
- polydb/advanced_query.py +5 -4
- polydb/aio.py +198 -0
- polydb/audit/AuditStorage.py +14 -19
- polydb/audit/__init__.py +2 -2
- polydb/audit/context.py +6 -5
- polydb/audit/manager.py +3 -3
- polydb/audit/models.py +3 -2
- polydb/base/NoSQLKVAdapter.py +22 -14
- polydb/base/ObjectStorageAdapter.py +3 -2
- polydb/base/QueueAdapter.py +2 -3
- polydb/base/SecretsAdapter.py +2 -1
- polydb/base/SharedFilesAdapter.py +2 -3
- polydb/batch.py +38 -49
- polydb/cache.py +5 -2
- polydb/cloudDatabaseFactory.py +59 -22
- polydb/databaseFactory.py +7 -13
- polydb/errors.py +2 -0
- polydb/json_safe.py +1 -2
- polydb/models.py +1 -1
- polydb/monitoring.py +27 -15
- polydb/multitenancy.py +50 -59
- polydb/observability/logging.py +0 -1
- polydb/overflow_gc.py +179 -0
- polydb/query.py +3 -1
- polydb/retry.py +1 -1
- polydb/schema.py +45 -60
- polydb/security.py +7 -9
- polydb/services/__init__.py +2 -2
- polydb/services/compliance_service.py +10 -4
- polydb/services/security_service.py +9 -12
- polydb/types.py +28 -62
- polydb/utils.py +7 -8
- polydb/validation.py +50 -52
- altcodepro_polydb_python-2.5.9.dist-info/RECORD +0 -73
- polydb/decorators.py +0 -1040
- {altcodepro_polydb_python-2.5.9.dist-info → altcodepro_polydb_python-2.5.11.dist-info}/WHEEL +0 -0
- {altcodepro_polydb_python-2.5.9.dist-info → altcodepro_polydb_python-2.5.11.dist-info}/licenses/LICENSE +0 -0
- {altcodepro_polydb_python-2.5.9.dist-info → altcodepro_polydb_python-2.5.11.dist-info}/top_level.txt +0 -0
polydb/security.py
CHANGED
|
@@ -3,17 +3,15 @@
|
|
|
3
3
|
Security features: encryption, masking, row-level security
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from typing import Dict, Any, List, Optional, Callable, Union
|
|
7
|
-
from dataclasses import dataclass
|
|
8
|
-
import hashlib
|
|
9
6
|
import base64
|
|
10
|
-
import os
|
|
11
7
|
import json
|
|
12
|
-
from functools import wraps
|
|
13
8
|
import logging
|
|
9
|
+
import os
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
14
12
|
|
|
15
|
-
from .json_safe import json_safe
|
|
16
13
|
from .errors import EncryptionConfigError
|
|
14
|
+
from .json_safe import json_safe
|
|
17
15
|
|
|
18
16
|
logger = logging.getLogger(__name__)
|
|
19
17
|
|
|
@@ -98,8 +96,8 @@ class FieldEncryption:
|
|
|
98
96
|
if not active_raw:
|
|
99
97
|
raise EncryptionConfigError(
|
|
100
98
|
"Field encryption is enabled but POLYDB_ENCRYPTION_KEY is not set. "
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
'Generate one with: python -c "import os,base64; '
|
|
100
|
+
'print(base64.b64encode(os.urandom(32)).decode())" and store it securely '
|
|
103
101
|
"(e.g. Azure Key Vault / AWS Secrets Manager / GCP Secret Manager) - do not "
|
|
104
102
|
"commit it or let it be generated on the fly."
|
|
105
103
|
)
|
|
@@ -135,7 +133,7 @@ class FieldEncryption:
|
|
|
135
133
|
if not isinstance(encrypted_data, str) or not encrypted_data.startswith("encrypted:"):
|
|
136
134
|
return encrypted_data
|
|
137
135
|
|
|
138
|
-
payload = encrypted_data[len("encrypted:"):]
|
|
136
|
+
payload = encrypted_data[len("encrypted:") :]
|
|
139
137
|
# Legacy format (pre key-versioning) has no key id segment - decrypt
|
|
140
138
|
# with the active key for backward compatibility with existing data.
|
|
141
139
|
key_id, _, b64_blob = payload.partition(":")
|
polydb/services/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from .compliance_service import
|
|
2
|
-
from .security_service import
|
|
1
|
+
from .compliance_service import AuditEvent, ComplianceService
|
|
2
|
+
from .security_service import EncryptionConfigError, SecurityService
|
|
3
3
|
|
|
4
4
|
__all__ = [
|
|
5
5
|
"ComplianceService",
|
|
@@ -70,7 +70,8 @@ class ComplianceService:
|
|
|
70
70
|
logger.critical(
|
|
71
71
|
"compliance.audit_failure retry queue full — event dropped: "
|
|
72
72
|
"event_type=%s tenant_id=%s",
|
|
73
|
-
event.event_type,
|
|
73
|
+
event.event_type,
|
|
74
|
+
event.tenant_id,
|
|
74
75
|
)
|
|
75
76
|
|
|
76
77
|
async def start(self, *, writer: WriterFn) -> None:
|
|
@@ -122,14 +123,19 @@ class ComplianceService:
|
|
|
122
123
|
logger.critical(
|
|
123
124
|
"compliance.audit_failure retry queue full on re-enqueue — event lost: "
|
|
124
125
|
"event_type=%s tenant_id=%s",
|
|
125
|
-
event.event_type,
|
|
126
|
+
event.event_type,
|
|
127
|
+
event.tenant_id,
|
|
126
128
|
)
|
|
127
129
|
|
|
128
130
|
def _record_failure(self, event: AuditEvent, exc: Exception, step: str) -> None:
|
|
129
131
|
logger.error(
|
|
130
132
|
"compliance.audit_failure step=%s event_type=%s tenant_id=%s resource=%s/%s error=%s",
|
|
131
|
-
step,
|
|
132
|
-
event.
|
|
133
|
+
step,
|
|
134
|
+
event.event_type,
|
|
135
|
+
event.tenant_id,
|
|
136
|
+
event.resource_type,
|
|
137
|
+
event.resource_id,
|
|
138
|
+
exc,
|
|
133
139
|
)
|
|
134
140
|
if self._metrics:
|
|
135
141
|
try:
|
|
@@ -78,11 +78,10 @@ class SecurityService:
|
|
|
78
78
|
def encrypt(self, value: str) -> str:
|
|
79
79
|
"""Encrypt *value*. Returns Fernet token (URL-safe base64 string)."""
|
|
80
80
|
if not self._key:
|
|
81
|
-
raise EncryptionConfigError(
|
|
82
|
-
f"Cannot encrypt: {self.ENV_KEY} is not set."
|
|
83
|
-
)
|
|
81
|
+
raise EncryptionConfigError(f"Cannot encrypt: {self.ENV_KEY} is not set.")
|
|
84
82
|
try:
|
|
85
83
|
from cryptography.fernet import Fernet
|
|
84
|
+
|
|
86
85
|
return Fernet(self._key).encrypt(value.encode("utf-8")).decode("ascii")
|
|
87
86
|
except ImportError:
|
|
88
87
|
raise EncryptionConfigError(
|
|
@@ -93,16 +92,13 @@ class SecurityService:
|
|
|
93
92
|
def decrypt(self, value: str) -> str:
|
|
94
93
|
"""Decrypt a Fernet token produced by :meth:`encrypt`."""
|
|
95
94
|
if not self._key:
|
|
96
|
-
raise EncryptionConfigError(
|
|
97
|
-
f"Cannot decrypt: {self.ENV_KEY} is not set."
|
|
98
|
-
)
|
|
95
|
+
raise EncryptionConfigError(f"Cannot decrypt: {self.ENV_KEY} is not set.")
|
|
99
96
|
try:
|
|
100
97
|
from cryptography.fernet import Fernet
|
|
98
|
+
|
|
101
99
|
return Fernet(self._key).decrypt(value.encode("ascii")).decode("utf-8")
|
|
102
100
|
except ImportError:
|
|
103
|
-
raise EncryptionConfigError(
|
|
104
|
-
"cryptography package is required for field encryption."
|
|
105
|
-
)
|
|
101
|
+
raise EncryptionConfigError("cryptography package is required for field encryption.")
|
|
106
102
|
except Exception as exc:
|
|
107
103
|
raise ValueError(f"Decryption failed: {exc}") from exc
|
|
108
104
|
|
|
@@ -122,9 +118,10 @@ class SecurityService:
|
|
|
122
118
|
if len(decoded) != 32:
|
|
123
119
|
logger.error(
|
|
124
120
|
"%s decoded to %d bytes; Fernet requires exactly 32 bytes. "
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
cls.ENV_KEY,
|
|
121
|
+
'Generate a valid key with: python -c "'
|
|
122
|
+
'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"',
|
|
123
|
+
cls.ENV_KEY,
|
|
124
|
+
len(decoded),
|
|
128
125
|
)
|
|
129
126
|
return None
|
|
130
127
|
return key_bytes
|
polydb/types.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import Any, Dict, List, Optional, Protocol, Tuple, Union, runtime_checkable
|
|
5
|
+
from typing import Any, Dict, List, Literal, Optional, Protocol, Tuple, Union, runtime_checkable
|
|
6
6
|
|
|
7
7
|
from .query import QueryBuilder
|
|
8
8
|
|
|
@@ -15,6 +15,7 @@ Lookup = Dict[str, Any]
|
|
|
15
15
|
@dataclass(frozen=True)
|
|
16
16
|
class ModelMeta:
|
|
17
17
|
"""Model metadata"""
|
|
18
|
+
|
|
18
19
|
storage: StorageType
|
|
19
20
|
table: Optional[str] = None
|
|
20
21
|
collection: Optional[str] = None
|
|
@@ -28,72 +29,51 @@ class ModelMeta:
|
|
|
28
29
|
@runtime_checkable
|
|
29
30
|
class SQLAdapter(Protocol):
|
|
30
31
|
"""SQL adapter contract"""
|
|
31
|
-
|
|
32
|
+
|
|
32
33
|
def insert(self, table: str, data: JsonDict) -> JsonDict: ...
|
|
33
|
-
|
|
34
|
+
|
|
34
35
|
def select(
|
|
35
36
|
self,
|
|
36
37
|
table: str,
|
|
37
38
|
query: Optional[Lookup] = None,
|
|
38
39
|
limit: Optional[int] = None,
|
|
39
|
-
offset: Optional[int] = None
|
|
40
|
+
offset: Optional[int] = None,
|
|
40
41
|
) -> List[JsonDict]: ...
|
|
41
|
-
|
|
42
|
+
|
|
42
43
|
def select_page(
|
|
43
|
-
self,
|
|
44
|
-
table: str,
|
|
45
|
-
query: Lookup,
|
|
46
|
-
page_size: int,
|
|
47
|
-
continuation_token: Optional[str] = None
|
|
44
|
+
self, table: str, query: Lookup, page_size: int, continuation_token: Optional[str] = None
|
|
48
45
|
) -> Tuple[List[JsonDict], Optional[str]]: ...
|
|
49
|
-
|
|
50
|
-
def update(
|
|
51
|
-
|
|
52
|
-
table: str,
|
|
53
|
-
entity_id: Union[Any, Lookup],
|
|
54
|
-
data: JsonDict
|
|
55
|
-
) -> JsonDict: ...
|
|
56
|
-
|
|
46
|
+
|
|
47
|
+
def update(self, table: str, entity_id: Union[Any, Lookup], data: JsonDict) -> JsonDict: ...
|
|
48
|
+
|
|
57
49
|
def upsert(self, table: str, data: JsonDict) -> JsonDict: ...
|
|
58
|
-
|
|
59
|
-
def delete(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
) -> JsonDict: ...
|
|
64
|
-
|
|
65
|
-
def query_linq(
|
|
66
|
-
self,
|
|
67
|
-
table: str,
|
|
68
|
-
builder: QueryBuilder
|
|
69
|
-
) -> Union[List[JsonDict], int]: ...
|
|
70
|
-
|
|
50
|
+
|
|
51
|
+
def delete(self, table: str, entity_id: Union[Any, Lookup]) -> JsonDict: ...
|
|
52
|
+
|
|
53
|
+
def query_linq(self, table: str, builder: QueryBuilder) -> Union[List[JsonDict], int]: ...
|
|
54
|
+
|
|
71
55
|
def execute(self, sql: str, params: Optional[List[Any]] = None) -> None: ...
|
|
72
56
|
|
|
73
57
|
|
|
74
58
|
@runtime_checkable
|
|
75
59
|
class NoSQLKVAdapter(Protocol):
|
|
76
60
|
"""NoSQL KV adapter contract"""
|
|
77
|
-
|
|
61
|
+
|
|
78
62
|
def put(self, model: type, data: JsonDict) -> JsonDict: ...
|
|
79
|
-
|
|
63
|
+
|
|
80
64
|
def query(
|
|
81
65
|
self,
|
|
82
66
|
model: type,
|
|
83
67
|
query: Optional[Lookup] = None,
|
|
84
68
|
limit: Optional[int] = None,
|
|
85
69
|
no_cache: bool = False,
|
|
86
|
-
cache_ttl: Optional[int] = None
|
|
70
|
+
cache_ttl: Optional[int] = None,
|
|
87
71
|
) -> List[JsonDict]: ...
|
|
88
|
-
|
|
72
|
+
|
|
89
73
|
def query_page(
|
|
90
|
-
self,
|
|
91
|
-
model: type,
|
|
92
|
-
query: Lookup,
|
|
93
|
-
page_size: int,
|
|
94
|
-
continuation_token: Optional[str] = None
|
|
74
|
+
self, model: type, query: Lookup, page_size: int, continuation_token: Optional[str] = None
|
|
95
75
|
) -> Tuple[List[JsonDict], Optional[str]]: ...
|
|
96
|
-
|
|
76
|
+
|
|
97
77
|
def patch(
|
|
98
78
|
self,
|
|
99
79
|
model: type,
|
|
@@ -101,27 +81,13 @@ class NoSQLKVAdapter(Protocol):
|
|
|
101
81
|
data: JsonDict,
|
|
102
82
|
*,
|
|
103
83
|
etag: Optional[str] = None,
|
|
104
|
-
replace: bool = False
|
|
105
|
-
) -> JsonDict: ...
|
|
106
|
-
|
|
107
|
-
def upsert(
|
|
108
|
-
self,
|
|
109
|
-
model: type,
|
|
110
|
-
data: JsonDict,
|
|
111
|
-
*,
|
|
112
|
-
replace: bool = False
|
|
84
|
+
replace: bool = False,
|
|
113
85
|
) -> JsonDict: ...
|
|
114
|
-
|
|
86
|
+
|
|
87
|
+
def upsert(self, model: type, data: JsonDict, *, replace: bool = False) -> JsonDict: ...
|
|
88
|
+
|
|
115
89
|
def delete(
|
|
116
|
-
self,
|
|
117
|
-
model: type,
|
|
118
|
-
entity_id: Union[Any, Lookup],
|
|
119
|
-
*,
|
|
120
|
-
etag: Optional[str] = None
|
|
90
|
+
self, model: type, entity_id: Union[Any, Lookup], *, etag: Optional[str] = None
|
|
121
91
|
) -> JsonDict: ...
|
|
122
|
-
|
|
123
|
-
def query_linq(
|
|
124
|
-
self,
|
|
125
|
-
model: type,
|
|
126
|
-
builder: QueryBuilder
|
|
127
|
-
) -> Union[List[JsonDict], int]: ...
|
|
92
|
+
|
|
93
|
+
def query_linq(self, model: type, builder: QueryBuilder) -> Union[List[JsonDict], int]: ...
|
polydb/utils.py
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
Utility functions for validation and logging
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
import logging
|
|
6
7
|
import os
|
|
7
8
|
import re
|
|
8
|
-
import
|
|
9
|
-
|
|
9
|
+
from typing import Any, Dict
|
|
10
|
+
|
|
10
11
|
from .errors import ValidationError
|
|
11
12
|
|
|
12
13
|
# Loggers already configured by setup_logger(), so per-request adapter
|
|
@@ -19,7 +20,7 @@ def validate_table_name(table: str) -> str:
|
|
|
19
20
|
Validate table name to prevent SQL injection
|
|
20
21
|
Only allows alphanumeric, underscore, and hyphen
|
|
21
22
|
"""
|
|
22
|
-
if not re.match(r
|
|
23
|
+
if not re.match(r"^[a-zA-Z0-9_-]+$", table):
|
|
23
24
|
raise ValidationError(
|
|
24
25
|
f"Invalid table name: '{table}'. Only alphanumeric, underscore, and hyphen allowed."
|
|
25
26
|
)
|
|
@@ -31,7 +32,7 @@ def validate_column_name(column: str) -> str:
|
|
|
31
32
|
Validate column name to prevent SQL injection
|
|
32
33
|
Only allows alphanumeric and underscore
|
|
33
34
|
"""
|
|
34
|
-
if not re.match(r
|
|
35
|
+
if not re.match(r"^[a-zA-Z0-9_]+$", column):
|
|
35
36
|
raise ValidationError(
|
|
36
37
|
f"Invalid column name: '{column}'. Only alphanumeric and underscore allowed."
|
|
37
38
|
)
|
|
@@ -85,10 +86,8 @@ def setup_logger(name: str, level: int = logging.INFO) -> logging.Logger:
|
|
|
85
86
|
logger.handlers.clear()
|
|
86
87
|
|
|
87
88
|
handler = logging.StreamHandler()
|
|
88
|
-
formatter = logging.Formatter(
|
|
89
|
-
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
90
|
-
)
|
|
89
|
+
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
91
90
|
handler.setFormatter(formatter)
|
|
92
91
|
logger.addHandler(handler)
|
|
93
92
|
|
|
94
|
-
return logger
|
|
93
|
+
return logger
|
polydb/validation.py
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Model metadata validation and schema enforcement
|
|
4
4
|
"""
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from
|
|
7
|
+
from typing import Any, Dict, List, Type
|
|
8
|
+
|
|
9
|
+
from .errors import InvalidModelMetadataError
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
@dataclass
|
|
10
13
|
class ValidationResult:
|
|
@@ -15,83 +18,82 @@ class ValidationResult:
|
|
|
15
18
|
|
|
16
19
|
class ModelValidator:
|
|
17
20
|
"""Validates model metadata and schema"""
|
|
18
|
-
|
|
19
|
-
REQUIRED_FIELDS = {
|
|
20
|
-
VALID_STORAGE_TYPES = {
|
|
21
|
-
SQL_REQUIRED_FIELDS = {
|
|
22
|
-
NOSQL_OPTIONAL_FIELDS = {
|
|
23
|
-
|
|
21
|
+
|
|
22
|
+
REQUIRED_FIELDS = {"storage"}
|
|
23
|
+
VALID_STORAGE_TYPES = {"sql", "nosql"}
|
|
24
|
+
SQL_REQUIRED_FIELDS = {"table"}
|
|
25
|
+
NOSQL_OPTIONAL_FIELDS = {"collection", "pk_field", "rk_field"}
|
|
26
|
+
|
|
24
27
|
@classmethod
|
|
25
28
|
def validate_model(cls, model: Type) -> ValidationResult:
|
|
26
29
|
"""Comprehensive model validation"""
|
|
27
30
|
errors = []
|
|
28
31
|
warnings = []
|
|
29
|
-
|
|
32
|
+
|
|
30
33
|
# Check __polydb__ exists
|
|
31
|
-
if not hasattr(model,
|
|
34
|
+
if not hasattr(model, "__polydb__"):
|
|
32
35
|
errors.append(f"Model {model.__name__} missing __polydb__ metadata")
|
|
33
36
|
return ValidationResult(valid=False, errors=errors, warnings=warnings)
|
|
34
|
-
|
|
35
|
-
meta = getattr(model,
|
|
36
|
-
|
|
37
|
+
|
|
38
|
+
meta = getattr(model, "__polydb__")
|
|
39
|
+
|
|
37
40
|
# Check it's a dict
|
|
38
41
|
if not isinstance(meta, dict):
|
|
39
42
|
errors.append(f"__polydb__ must be a dict, got {type(meta)}")
|
|
40
43
|
return ValidationResult(valid=False, errors=errors, warnings=warnings)
|
|
41
|
-
|
|
44
|
+
|
|
42
45
|
# Check required fields
|
|
43
46
|
for field in cls.REQUIRED_FIELDS:
|
|
44
47
|
if field not in meta:
|
|
45
48
|
errors.append(f"Missing required field: {field}")
|
|
46
|
-
|
|
49
|
+
|
|
47
50
|
# Validate storage type
|
|
48
|
-
storage = meta.get(
|
|
51
|
+
storage = meta.get("storage")
|
|
49
52
|
if storage and storage not in cls.VALID_STORAGE_TYPES:
|
|
50
|
-
errors.append(
|
|
51
|
-
|
|
53
|
+
errors.append(
|
|
54
|
+
f"Invalid storage type: {storage}. Must be one of {cls.VALID_STORAGE_TYPES}"
|
|
55
|
+
)
|
|
56
|
+
|
|
52
57
|
# Storage-specific validation
|
|
53
|
-
if storage ==
|
|
58
|
+
if storage == "sql":
|
|
54
59
|
for field in cls.SQL_REQUIRED_FIELDS:
|
|
55
60
|
if field not in meta:
|
|
56
61
|
errors.append(f"SQL storage requires field: {field}")
|
|
57
|
-
|
|
58
|
-
elif storage ==
|
|
62
|
+
|
|
63
|
+
elif storage == "nosql":
|
|
59
64
|
# NoSQL has optional fields, warn if missing common ones
|
|
60
|
-
if
|
|
65
|
+
if "pk_field" not in meta:
|
|
61
66
|
warnings.append("No pk_field specified, will default to 'id'")
|
|
62
|
-
|
|
63
|
-
if not meta.get(
|
|
67
|
+
|
|
68
|
+
if not meta.get("collection") and not meta.get("table"):
|
|
64
69
|
warnings.append("No collection/table name specified, will use model name")
|
|
65
|
-
|
|
70
|
+
|
|
66
71
|
# Validate cache settings
|
|
67
|
-
if meta.get(
|
|
72
|
+
if meta.get("cache") and not isinstance(meta.get("cache_ttl"), (int, type(None))):
|
|
68
73
|
errors.append("cache_ttl must be an integer or None")
|
|
69
|
-
|
|
74
|
+
|
|
70
75
|
# Validate provider
|
|
71
|
-
valid_providers = {
|
|
72
|
-
provider = meta.get(
|
|
76
|
+
valid_providers = {"azure", "aws", "gcp", "vercel", "mongodb", "postgresql"}
|
|
77
|
+
provider = meta.get("provider")
|
|
73
78
|
if provider and provider.lower() not in valid_providers:
|
|
74
79
|
warnings.append(f"Unusual provider: {provider}")
|
|
75
|
-
|
|
76
|
-
return ValidationResult(
|
|
77
|
-
|
|
78
|
-
errors=errors,
|
|
79
|
-
warnings=warnings
|
|
80
|
-
)
|
|
81
|
-
|
|
80
|
+
|
|
81
|
+
return ValidationResult(valid=len(errors) == 0, errors=errors, warnings=warnings)
|
|
82
|
+
|
|
82
83
|
@classmethod
|
|
83
84
|
def validate_and_raise(cls, model: Type):
|
|
84
85
|
"""Validate and raise if invalid"""
|
|
85
86
|
result = cls.validate_model(model)
|
|
86
|
-
|
|
87
|
+
|
|
87
88
|
if not result.valid:
|
|
88
89
|
raise InvalidModelMetadataError(
|
|
89
90
|
f"Invalid model {model.__name__}: {', '.join(result.errors)}"
|
|
90
91
|
)
|
|
91
|
-
|
|
92
|
+
|
|
92
93
|
# Log warnings
|
|
93
94
|
if result.warnings:
|
|
94
95
|
import logging
|
|
96
|
+
|
|
95
97
|
logger = logging.getLogger(__name__)
|
|
96
98
|
for warning in result.warnings:
|
|
97
99
|
logger.warning(f"{model.__name__}: {warning}")
|
|
@@ -99,22 +101,22 @@ class ModelValidator:
|
|
|
99
101
|
|
|
100
102
|
class SchemaValidator:
|
|
101
103
|
"""Validates data against model schema"""
|
|
102
|
-
|
|
104
|
+
|
|
103
105
|
@staticmethod
|
|
104
106
|
def validate_data(model: Type, data: Dict[str, Any]) -> ValidationResult:
|
|
105
107
|
"""Validate data against model requirements"""
|
|
106
108
|
errors = []
|
|
107
109
|
warnings = []
|
|
108
|
-
|
|
109
|
-
meta = getattr(model,
|
|
110
|
-
|
|
110
|
+
|
|
111
|
+
meta = getattr(model, "__polydb__", {})
|
|
112
|
+
|
|
111
113
|
# Check required keys exist
|
|
112
|
-
pk_field = meta.get(
|
|
113
|
-
if pk_field not in data and not meta.get(
|
|
114
|
+
pk_field = meta.get("pk_field", "id")
|
|
115
|
+
if pk_field not in data and not meta.get("auto_generate"):
|
|
114
116
|
errors.append(f"Missing required field: {pk_field}")
|
|
115
|
-
|
|
117
|
+
|
|
116
118
|
# Check field types if type hints available
|
|
117
|
-
if hasattr(model,
|
|
119
|
+
if hasattr(model, "__annotations__"):
|
|
118
120
|
annotations = model.__annotations__
|
|
119
121
|
for field_name, expected_type in annotations.items():
|
|
120
122
|
if field_name in data:
|
|
@@ -123,9 +125,5 @@ class SchemaValidator:
|
|
|
123
125
|
warnings.append(
|
|
124
126
|
f"Field {field_name} expected {expected_type}, got {type(actual_value)}"
|
|
125
127
|
)
|
|
126
|
-
|
|
127
|
-
return ValidationResult(
|
|
128
|
-
valid=len(errors) == 0,
|
|
129
|
-
errors=errors,
|
|
130
|
-
warnings=warnings
|
|
131
|
-
)
|
|
128
|
+
|
|
129
|
+
return ValidationResult(valid=len(errors) == 0, errors=errors, warnings=warnings)
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
altcodepro_polydb_python-2.5.9.dist-info/licenses/LICENSE,sha256=9X8GLocsBwy-5aR5JGOt2SAMDDPs9Qv-YnqmHBHOXrw,1067
|
|
2
|
-
polydb/PolyDB.py,sha256=MG7-nV59zDvrUQwXNEgV4eetHCeW9TYY9DQqtxu_r7k,23543
|
|
3
|
-
polydb/__init__.py,sha256=JxB_TeZ2RfmcuZ7HOTeRPSpe4kA-Pxe25M6KN2MR8vw,1535
|
|
4
|
-
polydb/advanced_query.py,sha256=JZUQr4wyC9ydrcO9VbZiH2ueiQiC4dxOX9hzG_JaFhs,6931
|
|
5
|
-
polydb/batch.py,sha256=_DjWZa1ZXYSk6MLKqFe0eT7SYVRZtYNqZb9bI8Y2sao,4566
|
|
6
|
-
polydb/cache.py,sha256=9UUHhiCVSWHQ-x-Ac_kIHYAadj0AQeqE4x8dwtyYFII,11781
|
|
7
|
-
polydb/cloudDatabaseFactory.py,sha256=M2qtHJmZUr6ZSZY6RCOgC3MLmq5o9WJeKgVubU77chk,22943
|
|
8
|
-
polydb/databaseFactory.py,sha256=xbv3w9flbfkC_5QLFIVNoXs5WFEwH_lH3PARwmJxtxU,53214
|
|
9
|
-
polydb/decorators.py,sha256=L_WP2uXP_k8Ac49SUm-mthbM4jWI-XYfHXEyKzumOww,43062
|
|
10
|
-
polydb/errors.py,sha256=ynQ732utRGA3nFan0tv1CVNxZgV07r7lidpFf-wVhSU,1830
|
|
11
|
-
polydb/json_safe.py,sha256=R5PrqAGirqjYKPyy-8KH-lSXjLH0FPr2TSGozy4eheU,149
|
|
12
|
-
polydb/models.py,sha256=HPOdcg0F1rPONJsnbwja75FDQ-38o94U1KsLktngwoI,12600
|
|
13
|
-
polydb/monitoring.py,sha256=UMm3ybyRJjAQi-prXXMLl9zuHhnhMnYBzMD3XWK66y8,9571
|
|
14
|
-
polydb/multitenancy.py,sha256=BfnSJRRyRl4ObM4noXDJhsGJHLESXzvYRsdKwic7TQc,8135
|
|
15
|
-
polydb/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
polydb/query.py,sha256=72QbnQaljNGvIWktSD29BII0476MMjrJ-7dQmOcWuZc,8509
|
|
17
|
-
polydb/registry.py,sha256=RD_elvFXcmhTdCyZDm2f3ej0elxqhArnSJ2aO9k5VCU,2352
|
|
18
|
-
polydb/retry.py,sha256=tX8vniILKe1A_2MLXLACY5iGEBT_zSLYGggkzvxt618,4926
|
|
19
|
-
polydb/schema.py,sha256=hI7uJM4WvH31A-h0kGZhCD5mCYcnrNvEega--_kvafE,6889
|
|
20
|
-
polydb/security.py,sha256=KqV5cD19hLyIvykW2DkKCXuj1hBDLt3xQ1tFtz_sCQE,20202
|
|
21
|
-
polydb/types.py,sha256=XB_85Un8_aWt4dSfpjIGotHbK3KBY2WurQGXr9EOxWY,2992
|
|
22
|
-
polydb/utils.py,sha256=tS_KI1vt-VZ6JftbFxbnVxC0xpie1-zYikTGf1nSoFw,3183
|
|
23
|
-
polydb/validation.py,sha256=a1o1d02k3c6PWQwkBbw_0nEmIgrdB5RR8OcpNQMn4cA,4810
|
|
24
|
-
polydb/adapters/AWSSecretsManagerAdapter.py,sha256=W4B3xhQFcBAlNlwQ4uTgcDmRrOmZr6v-suC4qihTpJw,2747
|
|
25
|
-
polydb/adapters/AzureBlobStorageAdapter.py,sha256=4vD55Z8DBTzBK66jIJbo5bNMY-AQ61MlP0-P2Fv_JgQ,7083
|
|
26
|
-
polydb/adapters/AzureFileStorageAdapter.py,sha256=VZNprqlBXCuWUgtqClNT-NrQmRf-XFYEiRA2BLbf-Sc,7046
|
|
27
|
-
polydb/adapters/AzureKeyVaultAdapter.py,sha256=7Nizs9qGh6cytbZNgV78f-odOO4Z6sUlwm_HQYqt-pc,2339
|
|
28
|
-
polydb/adapters/AzureQueueAdapter.py,sha256=15w0QQMBM53NIg10MZqdoweJyYtYdgR-SbZVwFqx3BI,13660
|
|
29
|
-
polydb/adapters/AzureTableStorageAdapter.py,sha256=s367vYgA40CkqPmCCQvvKRsGHA-AAo8a4GnPFvGsNxo,32025
|
|
30
|
-
polydb/adapters/BlockchainBlobAdapter.py,sha256=D01Yua9mkKfaQrxKYApblIyI6DSP0dtNAh4Tav51HJ4,3299
|
|
31
|
-
polydb/adapters/BlockchainFileAdapter.py,sha256=G749xOVpG20HuKS8zCgi6PMjoJNu-YXK7zitygjLdzM,8335
|
|
32
|
-
polydb/adapters/BlockchainKVAdapter.py,sha256=UFYHyTgvdW-sZUBqyHEG3Cdx6wSTiF2QowEVWL3XPTg,4564
|
|
33
|
-
polydb/adapters/BlockchainQueueAdapter.py,sha256=XsN5ag9Wulaelfkb3RCuBuSBw_4MUx9Wi3Gd3DBn7ME,4196
|
|
34
|
-
polydb/adapters/DynamoDBAdapter.py,sha256=kULYZMa9hFxV2VHVO_VsJcTh9OqKR8vQg2ceO1OvYbk,19545
|
|
35
|
-
polydb/adapters/EFSAdapter.py,sha256=Ca7-Esm3KNVm90Qo6tWmmftwk8Nq54DIzU3fAHX53io,2937
|
|
36
|
-
polydb/adapters/FirestoreAdapter.py,sha256=WCYZ6EThZgDGsdJcKXBFXHckdLnIY9jvtVYHSb3z7ao,15260
|
|
37
|
-
polydb/adapters/GCPFilestoreAdapter.py,sha256=yjFQQwsWYWc8mo8XwMViVTWb5_D--xAyTMvE-4AOpNM,3006
|
|
38
|
-
polydb/adapters/GCPPubSubAdapter.py,sha256=NcxzvCqjWWNFuGUU5LS5IYSV32cg_AGwGmSBn_gXE6Y,10685
|
|
39
|
-
polydb/adapters/GCPSecretManagerAdapter.py,sha256=ZFL2FGh7uTaBfpUo-HKXZAxS9s-w0w4yI_5I39KsG3A,2659
|
|
40
|
-
polydb/adapters/GCPStorageAdapter.py,sha256=9yS1Jhcn5_rCRdZ5uOqcRW6Ba-UNb6VOYpwENP-C6Qk,7133
|
|
41
|
-
polydb/adapters/KafkaQueueAdapter.py,sha256=JqzYGjeJtSLlL1jBqwWmBlnFKwwGIQUQzhsuM9wujm4,14411
|
|
42
|
-
polydb/adapters/MongoDBAdapter.py,sha256=qB7RwQb3tvSLLPBiKV_BoAHCvl8Me3C02ieunj01Iew,11788
|
|
43
|
-
polydb/adapters/PostgreSQLAdapter.py,sha256=tEzwFB-ENSOfOqDcGK5MUCN7cU7k4c1vFgm1fJUYf3A,43877
|
|
44
|
-
polydb/adapters/RabbitMQAdapter.py,sha256=c6usZrKgvUYwkfhcGjGFPjZkCKscU2xH9Fh9Ov9frYE,19903
|
|
45
|
-
polydb/adapters/S3Adapter.py,sha256=5R0zHAL2SkGFjp1L3bp-IU468bXYdSf6nKx974MN104,7586
|
|
46
|
-
polydb/adapters/S3CompatibleAdapter.py,sha256=jpafqbAjA8-irdXBrfXa1QJySIzrcUQ6UrFt5h5FAEc,7006
|
|
47
|
-
polydb/adapters/SQSAdapter.py,sha256=GhbbQKGA8P5teVdkeMZCl6aeH_8Dqi5qQJiJecwgV0A,8133
|
|
48
|
-
polydb/adapters/VaultAdapter.py,sha256=6R1hpn-BlPEm4fwoKc6304GmLrQeJohFmlqijlolq68,2716
|
|
49
|
-
polydb/adapters/VercelBlobAdapter.py,sha256=Hu1u8vE-3rvJMBDotnPKeRoVaH1Vl9PdM1hBKphgj2w,6983
|
|
50
|
-
polydb/adapters/VercelFileAdapter.py,sha256=-fLRCi0AUbyXAR4nkHCV-wXkociHF2hzzEDqWtixIpE,1033
|
|
51
|
-
polydb/adapters/VercelKVAdapter.py,sha256=jcuhXUcvhd3-QJUu1C_P4lN8DMu6qmjNQa3HwkzYcAE,10005
|
|
52
|
-
polydb/adapters/VercelQueueAdapter.py,sha256=lpfKdl-BPiQZJlwlyichTIWwj2iX9iEykAgoUd2wxmI,3894
|
|
53
|
-
polydb/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
|
-
polydb/audit/AuditStorage.py,sha256=6luCS7hTNL5MH2kkn6QlBlBsH-3FLuEFwP-cmkugRss,10469
|
|
55
|
-
polydb/audit/__init__.py,sha256=Z7-y5djq3glQ2Yun6nj-13Efpj3oGz9Qc0veS2g06Y4,245
|
|
56
|
-
polydb/audit/context.py,sha256=-A1FMtmr-2snVAHpTrVT80u-D_MCaqX6AoV4Ku2bz_o,1955
|
|
57
|
-
polydb/audit/manager.py,sha256=KzaaOf5bDfr4M-CkCAZBG_U_4xIBCKDLRAf3hsm-DAk,1236
|
|
58
|
-
polydb/audit/models.py,sha256=cSDkzW22FnsMvjQFvLQACQUm9lYFcnJ2Ew5mngBF9yQ,7043
|
|
59
|
-
polydb/base/NoSQLKVAdapter.py,sha256=U64nXaeHd40GCZV4DQnogZRUxvI8HHLEhNBMv4oddq4,14491
|
|
60
|
-
polydb/base/ObjectStorageAdapter.py,sha256=VeJ3qXET6H0xd3lJpE8-WSsKs8EyK9S0-9VNR0EJzME,2288
|
|
61
|
-
polydb/base/QueueAdapter.py,sha256=rphkdq9LEErwAKbmapUiHDshiOvgzvS5ZgFoOS8m9xc,8207
|
|
62
|
-
polydb/base/SecretsAdapter.py,sha256=HVk0H9637BkOJTykZNZbF2wKtSkFFj_-BZVpING6Hs4,989
|
|
63
|
-
polydb/base/SharedFilesAdapter.py,sha256=kXbJmtn_cwEyAZ-1AvFrmesCLSwu43ycTV3S4BmwrO4,853
|
|
64
|
-
polydb/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
|
-
polydb/observability/__init__.py,sha256=ZIMx5ODCBZI_FnROJFb11Q9E5kRq0ZcCp6_RmlwG53k,122
|
|
66
|
-
polydb/observability/logging.py,sha256=gv1SshNjaSkmR98Uv7Qbwq3gkeQUzVCA4ichqHKL690,3923
|
|
67
|
-
polydb/services/__init__.py,sha256=nMolMXzmhejRSm7f42kU9fCHF0GIpqpejN2kywuBsUQ,241
|
|
68
|
-
polydb/services/compliance_service.py,sha256=-0ncX7PrXy4-NuyWXIv65cLUsqz0jXzb3LtsD8rlrXE,4983
|
|
69
|
-
polydb/services/security_service.py,sha256=q7GzK6sbMnBBsc4fuL4oXTd-ui2b-MYQyrWTBvdVNSk,5062
|
|
70
|
-
altcodepro_polydb_python-2.5.9.dist-info/METADATA,sha256=g6kOe5L6nI43-JRAMXNTkzAN0jWb5w0-81zAuohzqtw,15598
|
|
71
|
-
altcodepro_polydb_python-2.5.9.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
72
|
-
altcodepro_polydb_python-2.5.9.dist-info/top_level.txt,sha256=WgLFWJoYjUhwvyPxJFl6jYLrVFuBJDX3OABf4ocwk_E,7
|
|
73
|
-
altcodepro_polydb_python-2.5.9.dist-info/RECORD,,
|