altcodepro-polydb-python 2.3.17__py3-none-any.whl → 2.3.20__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.3.17.dist-info → altcodepro_polydb_python-2.3.20.dist-info}/METADATA +1 -1
- {altcodepro_polydb_python-2.3.17.dist-info → altcodepro_polydb_python-2.3.20.dist-info}/RECORD +10 -10
- polydb/__init__.py +0 -2
- polydb/adapters/PostgreSQLAdapter.py +51 -45
- polydb/databaseFactory.py +21 -101
- polydb/decorators.py +1031 -12
- polydb/retry.py +17 -7
- {altcodepro_polydb_python-2.3.17.dist-info → altcodepro_polydb_python-2.3.20.dist-info}/WHEEL +0 -0
- {altcodepro_polydb_python-2.3.17.dist-info → altcodepro_polydb_python-2.3.20.dist-info}/licenses/LICENSE +0 -0
- {altcodepro_polydb_python-2.3.17.dist-info → altcodepro_polydb_python-2.3.20.dist-info}/top_level.txt +0 -0
{altcodepro_polydb_python-2.3.17.dist-info → altcodepro_polydb_python-2.3.20.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: altcodepro-polydb-python
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.20
|
|
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
|
{altcodepro_polydb_python-2.3.17.dist-info → altcodepro_polydb_python-2.3.20.dist-info}/RECORD
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
altcodepro_polydb_python-2.3.
|
|
1
|
+
altcodepro_polydb_python-2.3.20.dist-info/licenses/LICENSE,sha256=9X8GLocsBwy-5aR5JGOt2SAMDDPs9Qv-YnqmHBHOXrw,1067
|
|
2
2
|
polydb/PolyDB.py,sha256=MG7-nV59zDvrUQwXNEgV4eetHCeW9TYY9DQqtxu_r7k,23543
|
|
3
|
-
polydb/__init__.py,sha256=
|
|
3
|
+
polydb/__init__.py,sha256=e0tWk_og6VMgsTsoZbLc32yaIB0DBh7JpOt9t_jNTtk,1397
|
|
4
4
|
polydb/advanced_query.py,sha256=cxMB-EB-qT3bWXJlhmjnMCUtrzogORWyoEfS50Dy7go,4280
|
|
5
5
|
polydb/batch.py,sha256=_DjWZa1ZXYSk6MLKqFe0eT7SYVRZtYNqZb9bI8Y2sao,4566
|
|
6
6
|
polydb/cache.py,sha256=JBXF1XEK-fY80ar8SDE893Z1Z116YtXAEG0PaJ0Nkcw,7658
|
|
7
7
|
polydb/cloudDatabaseFactory.py,sha256=Gp6L__YtgrkGahD8B7ItzXMHCoj2ZUGDjXLS9w0TujY,17780
|
|
8
|
-
polydb/databaseFactory.py,sha256=
|
|
9
|
-
polydb/decorators.py,sha256=
|
|
8
|
+
polydb/databaseFactory.py,sha256=u20sy8bA09A0jTO2vLxGznssdcaxChQf21FBpooL2oA,40265
|
|
9
|
+
polydb/decorators.py,sha256=L_WP2uXP_k8Ac49SUm-mthbM4jWI-XYfHXEyKzumOww,43062
|
|
10
10
|
polydb/errors.py,sha256=rcFeBH0cenjJ86v0cmDc2Yjj4R019pLCBcTeSC4qps4,1428
|
|
11
11
|
polydb/json_safe.py,sha256=R5PrqAGirqjYKPyy-8KH-lSXjLH0FPr2TSGozy4eheU,149
|
|
12
12
|
polydb/models.py,sha256=9uu_BaJ95194n-vnd0Rx9KLc6aPS-mxn10P4W5grUcI,8155
|
|
@@ -15,7 +15,7 @@ polydb/multitenancy.py,sha256=9kyY98RpKg8xDy9ejB_MyV_YzF7eZd4uxashw5S8vlg,6408
|
|
|
15
15
|
polydb/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
polydb/query.py,sha256=3oWgRXIYLItmd_R-7u78hCdUgUaoiFmjVg38vi2-rVA,6600
|
|
17
17
|
polydb/registry.py,sha256=RD_elvFXcmhTdCyZDm2f3ej0elxqhArnSJ2aO9k5VCU,2352
|
|
18
|
-
polydb/retry.py,sha256=
|
|
18
|
+
polydb/retry.py,sha256=OfmNglrmL6AQlJPmXNdy9A9lRkH0P-vZ5BUuMiUM__4,3795
|
|
19
19
|
polydb/schema.py,sha256=VrOayX6V6AD2Qh3-lm4ZVPTpI24e4V52IYheZf2rNQ4,5812
|
|
20
20
|
polydb/security.py,sha256=9ju-hc6Y1sxobCoV_mZ3ZWroUD73LodyTLVMhY_HeKU,16360
|
|
21
21
|
polydb/types.py,sha256=XB_85Un8_aWt4dSfpjIGotHbK3KBY2WurQGXr9EOxWY,2992
|
|
@@ -36,7 +36,7 @@ polydb/adapters/GCPFilestoreAdapter.py,sha256=yjFQQwsWYWc8mo8XwMViVTWb5_D--xAyTM
|
|
|
36
36
|
polydb/adapters/GCPPubSubAdapter.py,sha256=7XNots2VA0ReEDku-rjg-OTYmftIpx5UgnXYDdXNkOo,8692
|
|
37
37
|
polydb/adapters/GCPStorageAdapter.py,sha256=9yS1Jhcn5_rCRdZ5uOqcRW6Ba-UNb6VOYpwENP-C6Qk,7133
|
|
38
38
|
polydb/adapters/MongoDBAdapter.py,sha256=vX3SAHDLbTnHABGesES9N-gYSQqPqdqFLJgd7pYWZzw,7471
|
|
39
|
-
polydb/adapters/PostgreSQLAdapter.py,sha256=
|
|
39
|
+
polydb/adapters/PostgreSQLAdapter.py,sha256=SUwtJ5GV1ec29Q5Ww6cehf3zlrhFhzP2b-TMFe7A0uM,31139
|
|
40
40
|
polydb/adapters/S3Adapter.py,sha256=5R0zHAL2SkGFjp1L3bp-IU468bXYdSf6nKx974MN104,7586
|
|
41
41
|
polydb/adapters/S3CompatibleAdapter.py,sha256=jpafqbAjA8-irdXBrfXa1QJySIzrcUQ6UrFt5h5FAEc,7006
|
|
42
42
|
polydb/adapters/SQSAdapter.py,sha256=1vfbNoqIDy-b8t2xcxy91SoxSYBPFDfUh7yCQWxdS84,5778
|
|
@@ -55,7 +55,7 @@ polydb/base/ObjectStorageAdapter.py,sha256=VeJ3qXET6H0xd3lJpE8-WSsKs8EyK9S0-9VNR
|
|
|
55
55
|
polydb/base/QueueAdapter.py,sha256=jFgyG-SUK4nhRNxm2NbzUbwnA9b_5iAC-ikLSUpXRwk,799
|
|
56
56
|
polydb/base/SharedFilesAdapter.py,sha256=kXbJmtn_cwEyAZ-1AvFrmesCLSwu43ycTV3S4BmwrO4,853
|
|
57
57
|
polydb/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
58
|
-
altcodepro_polydb_python-2.3.
|
|
59
|
-
altcodepro_polydb_python-2.3.
|
|
60
|
-
altcodepro_polydb_python-2.3.
|
|
61
|
-
altcodepro_polydb_python-2.3.
|
|
58
|
+
altcodepro_polydb_python-2.3.20.dist-info/METADATA,sha256=UwAAWPqWOC9nVRjTXS_3_8n1QqRpZ-bKIhKc_55fEXo,12359
|
|
59
|
+
altcodepro_polydb_python-2.3.20.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
60
|
+
altcodepro_polydb_python-2.3.20.dist-info/top_level.txt,sha256=WgLFWJoYjUhwvyPxJFl6jYLrVFuBJDX3OABf4ocwk_E,7
|
|
61
|
+
altcodepro_polydb_python-2.3.20.dist-info/RECORD,,
|
polydb/__init__.py
CHANGED
|
@@ -9,7 +9,6 @@ __version__ = "2.2.3"
|
|
|
9
9
|
from .cloudDatabaseFactory import CloudDatabaseFactory
|
|
10
10
|
from .databaseFactory import DatabaseFactory
|
|
11
11
|
from .models import CloudProvider, PartitionConfig
|
|
12
|
-
from .decorators import polydb_model
|
|
13
12
|
from .query import QueryBuilder, Operator
|
|
14
13
|
from .audit.context import AuditContext
|
|
15
14
|
from .cache import RedisCacheEngine as CacheEngine
|
|
@@ -36,7 +35,6 @@ __all__ = [
|
|
|
36
35
|
# Models & Config
|
|
37
36
|
"CloudProvider",
|
|
38
37
|
"PartitionConfig",
|
|
39
|
-
"polydb_model",
|
|
40
38
|
# Query
|
|
41
39
|
"QueryBuilder",
|
|
42
40
|
"Operator",
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# src/polydb/adapters/postgres.py
|
|
2
|
-
import datetime
|
|
3
|
-
from decimal import Decimal
|
|
4
2
|
import os
|
|
5
3
|
import threading
|
|
6
4
|
from typing import Any, Iterator, List, Optional, Tuple, Union
|
|
7
5
|
import hashlib
|
|
8
6
|
from contextlib import contextmanager
|
|
9
7
|
import json
|
|
8
|
+
from decimal import Decimal
|
|
10
9
|
from datetime import datetime, date
|
|
11
10
|
|
|
12
11
|
import psycopg2.extensions
|
|
12
|
+
from psycopg2.extras import Json
|
|
13
13
|
|
|
14
14
|
from ..errors import DatabaseError, ConnectionError
|
|
15
15
|
from ..retry import retry
|
|
@@ -158,7 +158,7 @@ class PostgreSQLAdapter:
|
|
|
158
158
|
if self._pool:
|
|
159
159
|
try:
|
|
160
160
|
self._pool.closeall()
|
|
161
|
-
except:
|
|
161
|
+
except Exception:
|
|
162
162
|
pass
|
|
163
163
|
self._pool = None
|
|
164
164
|
self._initialize_pool()
|
|
@@ -194,8 +194,9 @@ class PostgreSQLAdapter:
|
|
|
194
194
|
# ---------------------------------------------------------------------
|
|
195
195
|
def _json_safe(self, obj: Any):
|
|
196
196
|
"""
|
|
197
|
-
Ensure JSON serialization never fails.
|
|
198
|
-
|
|
197
|
+
Ensure JSON serialization never fails. Used only for Json() wrapping.
|
|
198
|
+
Recurses into dicts, lists, AND tuples so nested datetime/Decimal
|
|
199
|
+
values are made safe at any depth.
|
|
199
200
|
"""
|
|
200
201
|
if isinstance(obj, datetime):
|
|
201
202
|
return obj.isoformat()
|
|
@@ -205,66 +206,68 @@ class PostgreSQLAdapter:
|
|
|
205
206
|
return str(obj)
|
|
206
207
|
if isinstance(obj, dict):
|
|
207
208
|
return {k: self._json_safe(v) for k, v in obj.items()}
|
|
208
|
-
if isinstance(obj, list):
|
|
209
|
+
if isinstance(obj, (list, tuple)):
|
|
209
210
|
return [self._json_safe(v) for v in obj]
|
|
210
211
|
return obj
|
|
211
212
|
|
|
212
213
|
def _serialize_value(self, v: Any) -> Any:
|
|
213
214
|
"""
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
215
|
+
Serialize a value being WRITTEN to a column (insert / update SET /
|
|
216
|
+
upsert data values).
|
|
217
|
+
|
|
218
|
+
This platform provisions every ARRAY and OBJECT field as a JSONB
|
|
219
|
+
column (SchemaProvisioner._FIELD_TO_SQL_TYPE never emits a native
|
|
220
|
+
text[]/int[] column), so dicts AND lists/tuples are ALWAYS
|
|
221
|
+
JSON-encoded via psycopg2's Json adapter.
|
|
222
|
+
|
|
223
|
+
Json() handles empty {} and [] correctly ('{}'::jsonb / '[]'::jsonb),
|
|
224
|
+
handles nesting, and never produces a quoted string — which is what
|
|
225
|
+
broke the earlier native-list, json.dumps, and NULL-ify-empties
|
|
226
|
+
attempts. Empties are NOT turned into NULL: an empty JSONB array/
|
|
227
|
+
object is a valid, meaningful value.
|
|
224
228
|
"""
|
|
225
|
-
# NULL-ify empties so they're valid for TEXT[], JSONB, and plain columns alike.
|
|
226
|
-
from psycopg2.extras import Json
|
|
227
|
-
|
|
228
229
|
if v is None:
|
|
229
230
|
return None
|
|
230
|
-
if isinstance(v, (list, tuple))
|
|
231
|
-
return
|
|
232
|
-
if isinstance(v,
|
|
233
|
-
return
|
|
231
|
+
if isinstance(v, (dict, list, tuple)):
|
|
232
|
+
return Json(self._json_safe(v))
|
|
233
|
+
if isinstance(v, (datetime, date)):
|
|
234
|
+
return v
|
|
235
|
+
if isinstance(v, Decimal):
|
|
236
|
+
return float(v)
|
|
237
|
+
return v
|
|
234
238
|
|
|
235
|
-
|
|
239
|
+
def _serialize_param(self, v: Any) -> Any:
|
|
240
|
+
"""
|
|
241
|
+
Serialize a value used as a QUERY PARAMETER (WHERE values, IN / ANY
|
|
242
|
+
lists, LIMIT/OFFSET, raw execute() params).
|
|
243
|
+
|
|
244
|
+
Unlike _serialize_value, primitive lists/tuples are kept NATIVE so
|
|
245
|
+
that ``IN %s`` / ``= ANY(%s)`` parameters expand correctly. A dict
|
|
246
|
+
(or a list that contains dicts) is JSON-encoded so it can be compared
|
|
247
|
+
against a JSONB column.
|
|
248
|
+
"""
|
|
249
|
+
if v is None:
|
|
250
|
+
return None
|
|
236
251
|
if isinstance(v, dict):
|
|
237
252
|
return Json(self._json_safe(v))
|
|
238
|
-
|
|
239
|
-
# List: route by element type.
|
|
240
253
|
if isinstance(v, (list, tuple)):
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
# If ALL elements are primitives, send as native list for TEXT[]/INT[].
|
|
246
|
-
if all(isinstance(x, (str, int, float, bool, type(None))) for x in v):
|
|
247
|
-
return v
|
|
248
|
-
# Mixed / nested -> safest is JSONB
|
|
249
|
-
return Json(v)
|
|
250
|
-
|
|
251
|
-
# Datetime / date
|
|
254
|
+
seq = list(v)
|
|
255
|
+
if any(isinstance(x, dict) for x in seq):
|
|
256
|
+
return Json(self._json_safe(seq))
|
|
257
|
+
return seq # native -> psycopg2 expands for IN / ANY
|
|
252
258
|
if isinstance(v, (datetime, date)):
|
|
253
259
|
return v
|
|
254
|
-
|
|
255
|
-
# Decimal
|
|
256
260
|
if isinstance(v, Decimal):
|
|
257
261
|
return float(v)
|
|
258
|
-
|
|
259
262
|
return v
|
|
260
263
|
|
|
261
264
|
def _serialize_params(self, params: List[Any]) -> List[Any]:
|
|
262
|
-
return [self.
|
|
265
|
+
return [self._serialize_param(p) for p in params]
|
|
263
266
|
|
|
264
267
|
def _deserialize_row(self, row: JsonDict) -> JsonDict:
|
|
265
268
|
"""
|
|
266
|
-
Postgres JSON/JSONB often comes back as dict already depending on
|
|
267
|
-
If it comes as a string, try json.loads safely.
|
|
269
|
+
Postgres JSON/JSONB often comes back as dict/list already depending on
|
|
270
|
+
driver config. If it comes as a string, try json.loads safely.
|
|
268
271
|
"""
|
|
269
272
|
for k, v in list(row.items()):
|
|
270
273
|
if isinstance(v, str):
|
|
@@ -444,6 +447,7 @@ class PostgreSQLAdapter:
|
|
|
444
447
|
cursor = conn.cursor()
|
|
445
448
|
|
|
446
449
|
set_clause = ", ".join([f"{k} = %s" for k in data.keys()])
|
|
450
|
+
# SET values are written into columns -> write serializer (JSONB).
|
|
447
451
|
params: List[Any] = [self._serialize_value(v) for v in data.values()]
|
|
448
452
|
|
|
449
453
|
if isinstance(entity_id, dict):
|
|
@@ -454,7 +458,8 @@ class PostgreSQLAdapter:
|
|
|
454
458
|
where_parts.append(f"{k} IS NULL")
|
|
455
459
|
else:
|
|
456
460
|
where_parts.append(f"{k} = %s")
|
|
457
|
-
params.
|
|
461
|
+
# WHERE values are query params -> param serializer.
|
|
462
|
+
params.append(self._serialize_param(v))
|
|
458
463
|
where_clause = " AND ".join(where_parts)
|
|
459
464
|
else:
|
|
460
465
|
where_clause = "id = %s"
|
|
@@ -580,7 +585,8 @@ class PostgreSQLAdapter:
|
|
|
580
585
|
where_parts.append(f"{k} IS NULL")
|
|
581
586
|
else:
|
|
582
587
|
where_parts.append(f"{k} = %s")
|
|
583
|
-
params.
|
|
588
|
+
# WHERE values are query params -> param serializer.
|
|
589
|
+
params.append(self._serialize_param(v))
|
|
584
590
|
where_clause = " AND ".join(where_parts)
|
|
585
591
|
else:
|
|
586
592
|
where_clause = "id = %s"
|
polydb/databaseFactory.py
CHANGED
|
@@ -35,6 +35,7 @@ from .audit.context import AuditContext
|
|
|
35
35
|
from .query import Operator, QueryBuilder
|
|
36
36
|
from .cloudDatabaseFactory import CloudDatabaseFactory
|
|
37
37
|
import re as _re
|
|
38
|
+
|
|
38
39
|
logger = logging.getLogger(__name__)
|
|
39
40
|
|
|
40
41
|
_DEFAULT_RETRY = retry(
|
|
@@ -44,12 +45,12 @@ _DEFAULT_RETRY = retry(
|
|
|
44
45
|
)
|
|
45
46
|
|
|
46
47
|
_UNIQUE_VIOLATION_MARKERS = (
|
|
47
|
-
"23505",
|
|
48
|
-
"duplicate key value violates",
|
|
49
|
-
"unique constraint",
|
|
50
|
-
"UniqueViolation",
|
|
51
|
-
"Duplicate entry",
|
|
52
|
-
"UNIQUE constraint failed",
|
|
48
|
+
"23505", # Postgres SQLSTATE
|
|
49
|
+
"duplicate key value violates", # Postgres message
|
|
50
|
+
"unique constraint", # Postgres, generic
|
|
51
|
+
"UniqueViolation", # psycopg / SQLAlchemy class name
|
|
52
|
+
"Duplicate entry", # MySQL
|
|
53
|
+
"UNIQUE constraint failed", # SQLite
|
|
53
54
|
)
|
|
54
55
|
_UNIQUE_KEY_RE = _re.compile(r"Key \(([^)]+)\)=")
|
|
55
56
|
|
|
@@ -69,6 +70,8 @@ def _parse_unique_violation_columns(exc: BaseException) -> list[str]:
|
|
|
69
70
|
if not m:
|
|
70
71
|
return []
|
|
71
72
|
return [c.strip() for c in m.group(1).split(",") if c.strip()]
|
|
73
|
+
|
|
74
|
+
|
|
72
75
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
73
76
|
# ENGINE CONFIG
|
|
74
77
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -347,42 +350,6 @@ class DatabaseFactory:
|
|
|
347
350
|
result.setdefault("deleted_at", None)
|
|
348
351
|
return result
|
|
349
352
|
|
|
350
|
-
def _audit_safe(
|
|
351
|
-
self,
|
|
352
|
-
*,
|
|
353
|
-
action: str,
|
|
354
|
-
model: Union[type, str],
|
|
355
|
-
entity_id: Optional[Any],
|
|
356
|
-
meta: ModelMeta,
|
|
357
|
-
success: bool,
|
|
358
|
-
before: Optional[JsonDict],
|
|
359
|
-
after: Optional[JsonDict],
|
|
360
|
-
error: Optional[str],
|
|
361
|
-
engine_name: Optional[str] = None,
|
|
362
|
-
) -> None:
|
|
363
|
-
if not self._audit:
|
|
364
|
-
return
|
|
365
|
-
try:
|
|
366
|
-
changed = None
|
|
367
|
-
if before and after:
|
|
368
|
-
changed = [
|
|
369
|
-
k for k in set(before) | set(after) if before.get(k) != after.get(k)
|
|
370
|
-
] or None
|
|
371
|
-
self._audit.record(
|
|
372
|
-
action=action,
|
|
373
|
-
model=_model_name(model),
|
|
374
|
-
entity_id=str(entity_id) if entity_id else None,
|
|
375
|
-
storage_type=meta.storage,
|
|
376
|
-
provider=engine_name or self._provider_name,
|
|
377
|
-
success=success,
|
|
378
|
-
before=before,
|
|
379
|
-
after=after,
|
|
380
|
-
changed_fields=changed,
|
|
381
|
-
error=error,
|
|
382
|
-
)
|
|
383
|
-
except Exception as exc:
|
|
384
|
-
logger.error("Audit recording failed: %s", exc)
|
|
385
|
-
|
|
386
353
|
def _run(self, fn: Callable[[], Any]) -> Any:
|
|
387
354
|
return fn()
|
|
388
355
|
|
|
@@ -437,7 +404,8 @@ class DatabaseFactory:
|
|
|
437
404
|
where = {c: data[c] for c in conflict_cols}
|
|
438
405
|
logger.warning(
|
|
439
406
|
"insert %s hit unique violation on %s — falling through to update",
|
|
440
|
-
meta.table,
|
|
407
|
+
meta.table,
|
|
408
|
+
conflict_cols,
|
|
441
409
|
)
|
|
442
410
|
# Drop the conflict columns from the UPDATE SET clause — they're
|
|
443
411
|
# already the matching key.
|
|
@@ -445,8 +413,11 @@ class DatabaseFactory:
|
|
|
445
413
|
result = adapters.sql.update(meta.table, where, update_data)
|
|
446
414
|
else:
|
|
447
415
|
result = adapters.nosql.put(
|
|
448
|
-
|
|
449
|
-
|
|
416
|
+
(
|
|
417
|
+
model
|
|
418
|
+
if isinstance(model, type)
|
|
419
|
+
else type(name, (), {"__polydb__": meta.__dict__})
|
|
420
|
+
),
|
|
450
421
|
data,
|
|
451
422
|
)
|
|
452
423
|
entity_id = result.get("id")
|
|
@@ -457,6 +428,7 @@ class DatabaseFactory:
|
|
|
457
428
|
if self._enable_cache and self._cache:
|
|
458
429
|
self._cache.invalidate(name)
|
|
459
430
|
return after_plain
|
|
431
|
+
|
|
460
432
|
try:
|
|
461
433
|
monitor = (
|
|
462
434
|
PerformanceMonitor(self.metrics, "create", name, None) if self.metrics else None
|
|
@@ -467,21 +439,8 @@ class DatabaseFactory:
|
|
|
467
439
|
m.rows_affected = 1
|
|
468
440
|
return result
|
|
469
441
|
return self._run(_op)
|
|
470
|
-
except Exception
|
|
471
|
-
error = str(exc)
|
|
442
|
+
except Exception:
|
|
472
443
|
raise
|
|
473
|
-
finally:
|
|
474
|
-
self._audit_safe(
|
|
475
|
-
action="create",
|
|
476
|
-
model=model,
|
|
477
|
-
entity_id=entity_id,
|
|
478
|
-
meta=meta,
|
|
479
|
-
success=success,
|
|
480
|
-
before=None,
|
|
481
|
-
after=after_plain,
|
|
482
|
-
error=error,
|
|
483
|
-
engine_name=adapters.engine_name,
|
|
484
|
-
)
|
|
485
444
|
|
|
486
445
|
# ──────────────────────────────────────────────────────────────────────
|
|
487
446
|
# READ
|
|
@@ -645,21 +604,8 @@ class DatabaseFactory:
|
|
|
645
604
|
m.rows_affected = 1
|
|
646
605
|
return result
|
|
647
606
|
return self._run(_op)
|
|
648
|
-
except Exception
|
|
649
|
-
error = str(exc)
|
|
607
|
+
except Exception:
|
|
650
608
|
raise
|
|
651
|
-
finally:
|
|
652
|
-
self._audit_safe(
|
|
653
|
-
action="update",
|
|
654
|
-
model=model,
|
|
655
|
-
entity_id=entity_id if not isinstance(entity_id, dict) else None,
|
|
656
|
-
meta=meta,
|
|
657
|
-
success=success,
|
|
658
|
-
before=before,
|
|
659
|
-
after=after_plain,
|
|
660
|
-
error=error,
|
|
661
|
-
engine_name=adapters.engine_name,
|
|
662
|
-
)
|
|
663
609
|
|
|
664
610
|
# ──────────────────────────────────────────────────────────────────────
|
|
665
611
|
# UPSERT
|
|
@@ -715,21 +661,8 @@ class DatabaseFactory:
|
|
|
715
661
|
m.rows_affected = 1
|
|
716
662
|
return result
|
|
717
663
|
return self._run(_op)
|
|
718
|
-
except Exception
|
|
719
|
-
error = str(exc)
|
|
664
|
+
except Exception:
|
|
720
665
|
raise
|
|
721
|
-
finally:
|
|
722
|
-
self._audit_safe(
|
|
723
|
-
action="upsert",
|
|
724
|
-
model=model,
|
|
725
|
-
entity_id=None,
|
|
726
|
-
meta=meta,
|
|
727
|
-
success=success,
|
|
728
|
-
before=None,
|
|
729
|
-
after=after_plain,
|
|
730
|
-
error=error,
|
|
731
|
-
engine_name=adapters.engine_name,
|
|
732
|
-
)
|
|
733
666
|
|
|
734
667
|
# ──────────────────────────────────────────────────────────────────────
|
|
735
668
|
# DELETE
|
|
@@ -795,21 +728,8 @@ class DatabaseFactory:
|
|
|
795
728
|
m.rows_affected = 1
|
|
796
729
|
return result
|
|
797
730
|
return self._run(_op)
|
|
798
|
-
except Exception
|
|
799
|
-
error = str(exc)
|
|
731
|
+
except Exception:
|
|
800
732
|
raise
|
|
801
|
-
finally:
|
|
802
|
-
self._audit_safe(
|
|
803
|
-
action="delete",
|
|
804
|
-
model=model,
|
|
805
|
-
entity_id=entity_id if not isinstance(entity_id, dict) else None,
|
|
806
|
-
meta=meta,
|
|
807
|
-
success=success,
|
|
808
|
-
before=before,
|
|
809
|
-
after=None,
|
|
810
|
-
error=error,
|
|
811
|
-
engine_name=adapters.engine_name,
|
|
812
|
-
)
|
|
813
733
|
|
|
814
734
|
# ──────────────────────────────────────────────────────────────────────
|
|
815
735
|
# QUERY (LINQ-style)
|