altcodepro-polydb-python 2.5.4__py3-none-any.whl → 2.5.6__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.4.dist-info → altcodepro_polydb_python-2.5.6.dist-info}/METADATA +69 -20
- {altcodepro_polydb_python-2.5.4.dist-info → altcodepro_polydb_python-2.5.6.dist-info}/RECORD +28 -21
- polydb/__init__.py +1 -1
- polydb/adapters/AWSSecretsManagerAdapter.py +72 -0
- polydb/adapters/AzureKeyVaultAdapter.py +65 -0
- polydb/adapters/AzureQueueAdapter.py +82 -4
- polydb/adapters/DynamoDBAdapter.py +31 -16
- polydb/adapters/FirestoreAdapter.py +30 -13
- polydb/adapters/GCPPubSubAdapter.py +46 -0
- polydb/adapters/GCPSecretManagerAdapter.py +79 -0
- polydb/adapters/KafkaQueueAdapter.py +332 -0
- polydb/adapters/PostgreSQLAdapter.py +114 -6
- polydb/adapters/RabbitMQAdapter.py +465 -0
- polydb/adapters/SQSAdapter.py +60 -0
- polydb/adapters/VaultAdapter.py +75 -0
- polydb/adapters/VercelKVAdapter.py +38 -24
- polydb/adapters/VercelQueueAdapter.py +30 -2
- polydb/base/QueueAdapter.py +123 -1
- polydb/base/SecretsAdapter.py +30 -0
- polydb/cache.py +90 -0
- polydb/cloudDatabaseFactory.py +129 -15
- polydb/databaseFactory.py +225 -49
- polydb/errors.py +6 -0
- polydb/models.py +77 -0
- polydb/retry.py +7 -0
- {altcodepro_polydb_python-2.5.4.dist-info → altcodepro_polydb_python-2.5.6.dist-info}/WHEEL +0 -0
- {altcodepro_polydb_python-2.5.4.dist-info → altcodepro_polydb_python-2.5.6.dist-info}/licenses/LICENSE +0 -0
- {altcodepro_polydb_python-2.5.4.dist-info → altcodepro_polydb_python-2.5.6.dist-info}/top_level.txt +0 -0
{altcodepro_polydb_python-2.5.4.dist-info → altcodepro_polydb_python-2.5.6.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: altcodepro-polydb-python
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.6
|
|
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
|
|
@@ -27,19 +27,6 @@ Requires-Dist: psycopg2-binary>=2.9.11
|
|
|
27
27
|
Requires-Dist: tenacity>=9.1.4
|
|
28
28
|
Requires-Dist: redis>=6.4.0
|
|
29
29
|
Requires-Dist: python-dotenv>=1.1.1
|
|
30
|
-
Requires-Dist: azure-storage-queue>=12.15.0
|
|
31
|
-
Requires-Dist: azure-storage-blob>=12.29.0
|
|
32
|
-
Requires-Dist: boto3>=1.43.18
|
|
33
|
-
Requires-Dist: google-cloud-storage>=3.10.1
|
|
34
|
-
Requires-Dist: azure-storage-file-share>=12.25.0
|
|
35
|
-
Requires-Dist: azure-data-tables>=12.7.0
|
|
36
|
-
Requires-Dist: ipfshttpclient>=0.7.0
|
|
37
|
-
Requires-Dist: web3>=7.16.0
|
|
38
|
-
Requires-Dist: google-cloud-firestore>=2.27.0
|
|
39
|
-
Requires-Dist: google-cloud-pubsub>=2.38.0
|
|
40
|
-
Requires-Dist: pymongo>=4.17.0
|
|
41
|
-
Requires-Dist: build>=1.5.0
|
|
42
|
-
Requires-Dist: twine>=6.2.0
|
|
43
30
|
Provides-Extra: aws
|
|
44
31
|
Requires-Dist: boto3>=1.42.47; extra == "aws"
|
|
45
32
|
Requires-Dist: botocore>=1.42.47; extra == "aws"
|
|
@@ -49,6 +36,8 @@ Requires-Dist: azure-data-tables>=12.7.0; extra == "azure"
|
|
|
49
36
|
Requires-Dist: azure-storage-blob>=12.28.0; extra == "azure"
|
|
50
37
|
Requires-Dist: azure-storage-file-share>=12.24.0; extra == "azure"
|
|
51
38
|
Requires-Dist: azure-storage-queue>=12.15.0; extra == "azure"
|
|
39
|
+
Requires-Dist: azure-identity>=1.16.0; extra == "azure"
|
|
40
|
+
Requires-Dist: azure-keyvault-secrets>=4.8.0; extra == "azure"
|
|
52
41
|
Provides-Extra: gcp
|
|
53
42
|
Requires-Dist: google-api-core>=2.29.0; extra == "gcp"
|
|
54
43
|
Requires-Dist: google-auth>=2.48.0; extra == "gcp"
|
|
@@ -56,18 +45,27 @@ Requires-Dist: google-cloud-core>=2.5.0; extra == "gcp"
|
|
|
56
45
|
Requires-Dist: google-cloud-firestore>=2.23.0; extra == "gcp"
|
|
57
46
|
Requires-Dist: google-cloud-pubsub>=2.35.0; extra == "gcp"
|
|
58
47
|
Requires-Dist: google-cloud-storage>=3.9.0; extra == "gcp"
|
|
48
|
+
Requires-Dist: google-cloud-secret-manager>=2.20.0; extra == "gcp"
|
|
59
49
|
Provides-Extra: mongodb
|
|
60
50
|
Requires-Dist: pymongo>=4.16.0; extra == "mongodb"
|
|
61
51
|
Provides-Extra: rabbitmq
|
|
62
52
|
Requires-Dist: pika>=1.3.2; extra == "rabbitmq"
|
|
53
|
+
Provides-Extra: kafka
|
|
54
|
+
Requires-Dist: kafka-python>=2.0.2; extra == "kafka"
|
|
63
55
|
Provides-Extra: vercel
|
|
64
56
|
Requires-Dist: requests>=2.32.5; extra == "vercel"
|
|
57
|
+
Provides-Extra: blockchain
|
|
58
|
+
Requires-Dist: ipfshttpclient>=0.7.0; extra == "blockchain"
|
|
59
|
+
Requires-Dist: web3>=7.14.1; extra == "blockchain"
|
|
65
60
|
Provides-Extra: bolckchain
|
|
66
61
|
Requires-Dist: ipfshttpclient>=0.7.0; extra == "bolckchain"
|
|
67
62
|
Requires-Dist: web3>=7.14.1; extra == "bolckchain"
|
|
63
|
+
Provides-Extra: secrets
|
|
64
|
+
Requires-Dist: hvac>=2.1.0; extra == "secrets"
|
|
68
65
|
Provides-Extra: generic
|
|
69
66
|
Requires-Dist: pymongo>=4.16.0; extra == "generic"
|
|
70
67
|
Requires-Dist: pika>=1.3.2; extra == "generic"
|
|
68
|
+
Requires-Dist: kafka-python>=2.0.2; extra == "generic"
|
|
71
69
|
Requires-Dist: boto3>=1.42.47; extra == "generic"
|
|
72
70
|
Provides-Extra: security
|
|
73
71
|
Requires-Dist: cryptography>=42.0.0; extra == "security"
|
|
@@ -79,13 +77,18 @@ Requires-Dist: azure-data-tables>=12.7.0; extra == "all"
|
|
|
79
77
|
Requires-Dist: azure-storage-blob>=12.28.0; extra == "all"
|
|
80
78
|
Requires-Dist: azure-storage-file-share>=12.24.0; extra == "all"
|
|
81
79
|
Requires-Dist: azure-storage-queue>=12.15.0; extra == "all"
|
|
80
|
+
Requires-Dist: azure-identity>=1.16.0; extra == "all"
|
|
81
|
+
Requires-Dist: azure-keyvault-secrets>=4.8.0; extra == "all"
|
|
82
82
|
Requires-Dist: google-cloud-firestore>=2.23.0; extra == "all"
|
|
83
83
|
Requires-Dist: google-cloud-pubsub>=2.35.0; extra == "all"
|
|
84
84
|
Requires-Dist: google-cloud-storage>=3.9.0; extra == "all"
|
|
85
|
+
Requires-Dist: google-cloud-secret-manager>=2.20.0; extra == "all"
|
|
85
86
|
Requires-Dist: pymongo>=4.16.0; extra == "all"
|
|
86
87
|
Requires-Dist: pika>=1.3.2; extra == "all"
|
|
88
|
+
Requires-Dist: kafka-python>=2.0.2; extra == "all"
|
|
87
89
|
Requires-Dist: requests>=2.32.5; extra == "all"
|
|
88
90
|
Requires-Dist: cryptography>=42.0.0; extra == "all"
|
|
91
|
+
Requires-Dist: hvac>=2.1.0; extra == "all"
|
|
89
92
|
Provides-Extra: dev
|
|
90
93
|
Requires-Dist: black>=26.1.0; extra == "dev"
|
|
91
94
|
Requires-Dist: flake8>=7.3.0; extra == "dev"
|
|
@@ -97,6 +100,7 @@ Requires-Dist: pytest-asyncio>=1.2.0; extra == "test"
|
|
|
97
100
|
Requires-Dist: pytest-cov>=7.0.0; extra == "test"
|
|
98
101
|
Requires-Dist: pytest-mock>=3.15.1; extra == "test"
|
|
99
102
|
Requires-Dist: moto>=5.1.21; extra == "test"
|
|
103
|
+
Requires-Dist: hvac>=2.1.0; extra == "test"
|
|
100
104
|
Dynamic: license-file
|
|
101
105
|
|
|
102
106
|
|
|
@@ -291,19 +295,64 @@ Supported systems:
|
|
|
291
295
|
* AWS SQS
|
|
292
296
|
* Azure Queue
|
|
293
297
|
* Google Pub/Sub
|
|
294
|
-
* Vercel Queue
|
|
298
|
+
* Vercel Queue (cloud REST API, or a plain local `redis://` URL for
|
|
299
|
+
dev/test)
|
|
300
|
+
* Kafka
|
|
301
|
+
* RabbitMQ
|
|
295
302
|
* Blockchain event queues
|
|
296
303
|
|
|
297
|
-
Example
|
|
304
|
+
Example (method names below match `QueueAdapter`'s actual signatures —
|
|
305
|
+
`send`/`receive`/`ack`/`delete`, not `send_message`/`receive_message`):
|
|
298
306
|
|
|
299
307
|
```python
|
|
300
|
-
queue = factory.get_queue()
|
|
308
|
+
queue = factory.get_queue("rabbitmq")
|
|
301
309
|
|
|
302
|
-
queue.
|
|
303
|
-
|
|
304
|
-
queue.
|
|
310
|
+
message_id = queue.send({"task": "process"}, queue_name="jobs")
|
|
311
|
+
messages = queue.receive(queue_name="jobs", max_messages=1)
|
|
312
|
+
queue.ack(messages[0]["receipt_handle"], queue_name="jobs")
|
|
305
313
|
```
|
|
306
314
|
|
|
315
|
+
## Extended queue management (`nack`/`purge`/`declare`/`status`)
|
|
316
|
+
|
|
317
|
+
`QueueAdapter`'s base class also defines `nack`, `purge`, `declare`
|
|
318
|
+
(explicit provisioning ahead of first use, with optional dead-letter
|
|
319
|
+
queue wiring), and `status` — regular (non-abstract) base methods that
|
|
320
|
+
raise `NotImplementedError` by default, overridden only where a
|
|
321
|
+
subclass genuinely implements them. Honest status, verified against a
|
|
322
|
+
real local broker/client, not assumed:
|
|
323
|
+
|
|
324
|
+
* **RabbitMQAdapter** — all four implemented for real against a live
|
|
325
|
+
broker: `nack` is `basic_nack(requeue=True)` (immediate redelivery,
|
|
326
|
+
not dead-lettering); `purge` reads the real purged count off
|
|
327
|
+
`queue_purge`'s `Queue.PurgeOk` response; `declare` wires real AMQP
|
|
328
|
+
dead-lettering (`x-dead-letter-exchange`/`x-dead-letter-routing-key`
|
|
329
|
+
queue arguments — not a hand-rolled shadow re-router) when
|
|
330
|
+
`dead_letter_queue` is given, and `status` reads `message_count`/
|
|
331
|
+
`consumer_count` off a passive `queue_declare`. A message only
|
|
332
|
+
actually gets dead-lettered by a real reject-without-requeue
|
|
333
|
+
(`basic_nack`/`basic_reject` with `requeue=False`) or a TTL/
|
|
334
|
+
max-length policy — `nack()`'s own contract is "redeliver now", so it
|
|
335
|
+
deliberately does **not** trigger dead-lettering itself. `dlq_list`/
|
|
336
|
+
`dlq_replay` aren't separate adapter methods — they're just
|
|
337
|
+
`receive`/`send`+`ack` aimed at the DLQ's own queue name.
|
|
338
|
+
* **KafkaQueueAdapter** — only `nack` is implemented, and it's a
|
|
339
|
+
documented no-op: `receive()` already never commits an offset until
|
|
340
|
+
`ack()`/`delete()` does, so a received-but-unacked message is already
|
|
341
|
+
effectively "nacked". `purge`/`declare`/`status` are **not**
|
|
342
|
+
implemented — not practical against `kafka-python`'s client-level API
|
|
343
|
+
without broker admin tooling; they raise the base class's
|
|
344
|
+
`NotImplementedError`.
|
|
345
|
+
* **SQS / Azure Queue / GCP Pub/Sub / Blockchain** — none of the four
|
|
346
|
+
new methods are implemented; all raise the base class's
|
|
347
|
+
`NotImplementedError`. No credentials/emulator for any of these three
|
|
348
|
+
cloud providers, and no real blockchain node, were available where
|
|
349
|
+
this was built and tested.
|
|
350
|
+
* **Vercel Queue (local-redis mode)** — also not implemented for these
|
|
351
|
+
four, though flagged as a real, likely-tractable gap for later: Redis
|
|
352
|
+
Streams' `XPENDING`/`XCLAIM`/`XTRIM` could plausibly back `nack`/
|
|
353
|
+
`status`/`purge` for the local-redis mode specifically. Deliberately
|
|
354
|
+
left out of this round rather than half-implemented.
|
|
355
|
+
|
|
307
356
|
---
|
|
308
357
|
|
|
309
358
|
# Blockchain Storage
|
{altcodepro_polydb_python-2.5.4.dist-info → altcodepro_polydb_python-2.5.6.dist-info}/RECORD
RENAMED
|
@@ -1,49 +1,55 @@
|
|
|
1
|
-
altcodepro_polydb_python-2.5.
|
|
1
|
+
altcodepro_polydb_python-2.5.6.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=4-R-S5OD0aXGL2w2AJx5is4xqccdtWWOBrEHn18R51M,1535
|
|
4
4
|
polydb/advanced_query.py,sha256=JZUQr4wyC9ydrcO9VbZiH2ueiQiC4dxOX9hzG_JaFhs,6931
|
|
5
5
|
polydb/batch.py,sha256=_DjWZa1ZXYSk6MLKqFe0eT7SYVRZtYNqZb9bI8Y2sao,4566
|
|
6
|
-
polydb/cache.py,sha256=
|
|
7
|
-
polydb/cloudDatabaseFactory.py,sha256=
|
|
8
|
-
polydb/databaseFactory.py,sha256=
|
|
6
|
+
polydb/cache.py,sha256=9UUHhiCVSWHQ-x-Ac_kIHYAadj0AQeqE4x8dwtyYFII,11781
|
|
7
|
+
polydb/cloudDatabaseFactory.py,sha256=M2qtHJmZUr6ZSZY6RCOgC3MLmq5o9WJeKgVubU77chk,22943
|
|
8
|
+
polydb/databaseFactory.py,sha256=tznE9-CldL9sxo9ccCX2qBQYg56CEC6zDgx1DJXtQY0,51667
|
|
9
9
|
polydb/decorators.py,sha256=L_WP2uXP_k8Ac49SUm-mthbM4jWI-XYfHXEyKzumOww,43062
|
|
10
|
-
polydb/errors.py,sha256=
|
|
10
|
+
polydb/errors.py,sha256=ynQ732utRGA3nFan0tv1CVNxZgV07r7lidpFf-wVhSU,1830
|
|
11
11
|
polydb/json_safe.py,sha256=R5PrqAGirqjYKPyy-8KH-lSXjLH0FPr2TSGozy4eheU,149
|
|
12
|
-
polydb/models.py,sha256=
|
|
12
|
+
polydb/models.py,sha256=HPOdcg0F1rPONJsnbwja75FDQ-38o94U1KsLktngwoI,12600
|
|
13
13
|
polydb/monitoring.py,sha256=UMm3ybyRJjAQi-prXXMLl9zuHhnhMnYBzMD3XWK66y8,9571
|
|
14
14
|
polydb/multitenancy.py,sha256=BfnSJRRyRl4ObM4noXDJhsGJHLESXzvYRsdKwic7TQc,8135
|
|
15
15
|
polydb/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
polydb/query.py,sha256=72QbnQaljNGvIWktSD29BII0476MMjrJ-7dQmOcWuZc,8509
|
|
17
17
|
polydb/registry.py,sha256=RD_elvFXcmhTdCyZDm2f3ej0elxqhArnSJ2aO9k5VCU,2352
|
|
18
|
-
polydb/retry.py,sha256=
|
|
18
|
+
polydb/retry.py,sha256=tX8vniILKe1A_2MLXLACY5iGEBT_zSLYGggkzvxt618,4926
|
|
19
19
|
polydb/schema.py,sha256=hI7uJM4WvH31A-h0kGZhCD5mCYcnrNvEega--_kvafE,6889
|
|
20
20
|
polydb/security.py,sha256=KqV5cD19hLyIvykW2DkKCXuj1hBDLt3xQ1tFtz_sCQE,20202
|
|
21
21
|
polydb/types.py,sha256=XB_85Un8_aWt4dSfpjIGotHbK3KBY2WurQGXr9EOxWY,2992
|
|
22
22
|
polydb/utils.py,sha256=tS_KI1vt-VZ6JftbFxbnVxC0xpie1-zYikTGf1nSoFw,3183
|
|
23
23
|
polydb/validation.py,sha256=a1o1d02k3c6PWQwkBbw_0nEmIgrdB5RR8OcpNQMn4cA,4810
|
|
24
|
+
polydb/adapters/AWSSecretsManagerAdapter.py,sha256=W4B3xhQFcBAlNlwQ4uTgcDmRrOmZr6v-suC4qihTpJw,2747
|
|
24
25
|
polydb/adapters/AzureBlobStorageAdapter.py,sha256=4vD55Z8DBTzBK66jIJbo5bNMY-AQ61MlP0-P2Fv_JgQ,7083
|
|
25
26
|
polydb/adapters/AzureFileStorageAdapter.py,sha256=VZNprqlBXCuWUgtqClNT-NrQmRf-XFYEiRA2BLbf-Sc,7046
|
|
26
|
-
polydb/adapters/
|
|
27
|
+
polydb/adapters/AzureKeyVaultAdapter.py,sha256=7Nizs9qGh6cytbZNgV78f-odOO4Z6sUlwm_HQYqt-pc,2339
|
|
28
|
+
polydb/adapters/AzureQueueAdapter.py,sha256=15w0QQMBM53NIg10MZqdoweJyYtYdgR-SbZVwFqx3BI,13660
|
|
27
29
|
polydb/adapters/AzureTableStorageAdapter.py,sha256=jMgGjiaqI6bUeKRa--f8xHz1pb8j8AMGaP-VA5CxlYw,30126
|
|
28
30
|
polydb/adapters/BlockchainBlobAdapter.py,sha256=D01Yua9mkKfaQrxKYApblIyI6DSP0dtNAh4Tav51HJ4,3299
|
|
29
31
|
polydb/adapters/BlockchainFileAdapter.py,sha256=G749xOVpG20HuKS8zCgi6PMjoJNu-YXK7zitygjLdzM,8335
|
|
30
32
|
polydb/adapters/BlockchainKVAdapter.py,sha256=UFYHyTgvdW-sZUBqyHEG3Cdx6wSTiF2QowEVWL3XPTg,4564
|
|
31
33
|
polydb/adapters/BlockchainQueueAdapter.py,sha256=XsN5ag9Wulaelfkb3RCuBuSBw_4MUx9Wi3Gd3DBn7ME,4196
|
|
32
|
-
polydb/adapters/DynamoDBAdapter.py,sha256=
|
|
34
|
+
polydb/adapters/DynamoDBAdapter.py,sha256=kULYZMa9hFxV2VHVO_VsJcTh9OqKR8vQg2ceO1OvYbk,19545
|
|
33
35
|
polydb/adapters/EFSAdapter.py,sha256=Ca7-Esm3KNVm90Qo6tWmmftwk8Nq54DIzU3fAHX53io,2937
|
|
34
|
-
polydb/adapters/FirestoreAdapter.py,sha256=
|
|
36
|
+
polydb/adapters/FirestoreAdapter.py,sha256=WCYZ6EThZgDGsdJcKXBFXHckdLnIY9jvtVYHSb3z7ao,15260
|
|
35
37
|
polydb/adapters/GCPFilestoreAdapter.py,sha256=yjFQQwsWYWc8mo8XwMViVTWb5_D--xAyTMvE-4AOpNM,3006
|
|
36
|
-
polydb/adapters/GCPPubSubAdapter.py,sha256=
|
|
38
|
+
polydb/adapters/GCPPubSubAdapter.py,sha256=NcxzvCqjWWNFuGUU5LS5IYSV32cg_AGwGmSBn_gXE6Y,10685
|
|
39
|
+
polydb/adapters/GCPSecretManagerAdapter.py,sha256=ZFL2FGh7uTaBfpUo-HKXZAxS9s-w0w4yI_5I39KsG3A,2659
|
|
37
40
|
polydb/adapters/GCPStorageAdapter.py,sha256=9yS1Jhcn5_rCRdZ5uOqcRW6Ba-UNb6VOYpwENP-C6Qk,7133
|
|
41
|
+
polydb/adapters/KafkaQueueAdapter.py,sha256=JqzYGjeJtSLlL1jBqwWmBlnFKwwGIQUQzhsuM9wujm4,14411
|
|
38
42
|
polydb/adapters/MongoDBAdapter.py,sha256=qB7RwQb3tvSLLPBiKV_BoAHCvl8Me3C02ieunj01Iew,11788
|
|
39
|
-
polydb/adapters/PostgreSQLAdapter.py,sha256=
|
|
43
|
+
polydb/adapters/PostgreSQLAdapter.py,sha256=tEzwFB-ENSOfOqDcGK5MUCN7cU7k4c1vFgm1fJUYf3A,43877
|
|
44
|
+
polydb/adapters/RabbitMQAdapter.py,sha256=c6usZrKgvUYwkfhcGjGFPjZkCKscU2xH9Fh9Ov9frYE,19903
|
|
40
45
|
polydb/adapters/S3Adapter.py,sha256=5R0zHAL2SkGFjp1L3bp-IU468bXYdSf6nKx974MN104,7586
|
|
41
46
|
polydb/adapters/S3CompatibleAdapter.py,sha256=jpafqbAjA8-irdXBrfXa1QJySIzrcUQ6UrFt5h5FAEc,7006
|
|
42
|
-
polydb/adapters/SQSAdapter.py,sha256=
|
|
47
|
+
polydb/adapters/SQSAdapter.py,sha256=GhbbQKGA8P5teVdkeMZCl6aeH_8Dqi5qQJiJecwgV0A,8133
|
|
48
|
+
polydb/adapters/VaultAdapter.py,sha256=6R1hpn-BlPEm4fwoKc6304GmLrQeJohFmlqijlolq68,2716
|
|
43
49
|
polydb/adapters/VercelBlobAdapter.py,sha256=Hu1u8vE-3rvJMBDotnPKeRoVaH1Vl9PdM1hBKphgj2w,6983
|
|
44
50
|
polydb/adapters/VercelFileAdapter.py,sha256=-fLRCi0AUbyXAR4nkHCV-wXkociHF2hzzEDqWtixIpE,1033
|
|
45
|
-
polydb/adapters/VercelKVAdapter.py,sha256=
|
|
46
|
-
polydb/adapters/VercelQueueAdapter.py,sha256=
|
|
51
|
+
polydb/adapters/VercelKVAdapter.py,sha256=jcuhXUcvhd3-QJUu1C_P4lN8DMu6qmjNQa3HwkzYcAE,10005
|
|
52
|
+
polydb/adapters/VercelQueueAdapter.py,sha256=lpfKdl-BPiQZJlwlyichTIWwj2iX9iEykAgoUd2wxmI,3894
|
|
47
53
|
polydb/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
54
|
polydb/audit/AuditStorage.py,sha256=6luCS7hTNL5MH2kkn6QlBlBsH-3FLuEFwP-cmkugRss,10469
|
|
49
55
|
polydb/audit/__init__.py,sha256=Z7-y5djq3glQ2Yun6nj-13Efpj3oGz9Qc0veS2g06Y4,245
|
|
@@ -52,7 +58,8 @@ polydb/audit/manager.py,sha256=KzaaOf5bDfr4M-CkCAZBG_U_4xIBCKDLRAf3hsm-DAk,1236
|
|
|
52
58
|
polydb/audit/models.py,sha256=cSDkzW22FnsMvjQFvLQACQUm9lYFcnJ2Ew5mngBF9yQ,7043
|
|
53
59
|
polydb/base/NoSQLKVAdapter.py,sha256=U64nXaeHd40GCZV4DQnogZRUxvI8HHLEhNBMv4oddq4,14491
|
|
54
60
|
polydb/base/ObjectStorageAdapter.py,sha256=VeJ3qXET6H0xd3lJpE8-WSsKs8EyK9S0-9VNR0EJzME,2288
|
|
55
|
-
polydb/base/QueueAdapter.py,sha256=
|
|
61
|
+
polydb/base/QueueAdapter.py,sha256=rphkdq9LEErwAKbmapUiHDshiOvgzvS5ZgFoOS8m9xc,8207
|
|
62
|
+
polydb/base/SecretsAdapter.py,sha256=HVk0H9637BkOJTykZNZbF2wKtSkFFj_-BZVpING6Hs4,989
|
|
56
63
|
polydb/base/SharedFilesAdapter.py,sha256=kXbJmtn_cwEyAZ-1AvFrmesCLSwu43ycTV3S4BmwrO4,853
|
|
57
64
|
polydb/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
58
65
|
polydb/observability/__init__.py,sha256=ZIMx5ODCBZI_FnROJFb11Q9E5kRq0ZcCp6_RmlwG53k,122
|
|
@@ -60,7 +67,7 @@ polydb/observability/logging.py,sha256=gv1SshNjaSkmR98Uv7Qbwq3gkeQUzVCA4ichqHKL6
|
|
|
60
67
|
polydb/services/__init__.py,sha256=nMolMXzmhejRSm7f42kU9fCHF0GIpqpejN2kywuBsUQ,241
|
|
61
68
|
polydb/services/compliance_service.py,sha256=-0ncX7PrXy4-NuyWXIv65cLUsqz0jXzb3LtsD8rlrXE,4983
|
|
62
69
|
polydb/services/security_service.py,sha256=q7GzK6sbMnBBsc4fuL4oXTd-ui2b-MYQyrWTBvdVNSk,5062
|
|
63
|
-
altcodepro_polydb_python-2.5.
|
|
64
|
-
altcodepro_polydb_python-2.5.
|
|
65
|
-
altcodepro_polydb_python-2.5.
|
|
66
|
-
altcodepro_polydb_python-2.5.
|
|
70
|
+
altcodepro_polydb_python-2.5.6.dist-info/METADATA,sha256=HY5mBLl5CIIUAJuIwhFNhs1wwhyD9PLIxvUtIfvuwlE,15598
|
|
71
|
+
altcodepro_polydb_python-2.5.6.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
72
|
+
altcodepro_polydb_python-2.5.6.dist-info/top_level.txt,sha256=WgLFWJoYjUhwvyPxJFl6jYLrVFuBJDX3OABf4ocwk_E,7
|
|
73
|
+
altcodepro_polydb_python-2.5.6.dist-info/RECORD,,
|
polydb/__init__.py
CHANGED
|
@@ -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__ = "2.
|
|
7
|
+
__version__ = "2.5.6"
|
|
8
8
|
|
|
9
9
|
from .cloudDatabaseFactory import CloudDatabaseFactory
|
|
10
10
|
from .databaseFactory import DatabaseFactory
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# src/polydb/adapters/AWSSecretsManagerAdapter.py
|
|
2
|
+
import os
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from ..base.SecretsAdapter import SecretsAdapter
|
|
6
|
+
from ..retry import retry
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AWSSecretsManagerAdapter(SecretsAdapter):
|
|
10
|
+
def __init__(self, region: str = ""):
|
|
11
|
+
super().__init__()
|
|
12
|
+
|
|
13
|
+
self.region = region or os.getenv("AWS_REGION", "us-east-1")
|
|
14
|
+
self._client = None
|
|
15
|
+
self._initialize_client()
|
|
16
|
+
|
|
17
|
+
def _initialize_client(self) -> None:
|
|
18
|
+
import boto3
|
|
19
|
+
|
|
20
|
+
self._client = boto3.client("secretsmanager", region_name=self.region)
|
|
21
|
+
|
|
22
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(Exception,))
|
|
23
|
+
def get_secret(self, key: str) -> Optional[str]:
|
|
24
|
+
from botocore.exceptions import ClientError
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
resp = self._client.get_secret_value(SecretId=key)
|
|
28
|
+
except ClientError as exc:
|
|
29
|
+
if exc.response["Error"]["Code"] == "ResourceNotFoundException":
|
|
30
|
+
return None
|
|
31
|
+
raise
|
|
32
|
+
return resp.get("SecretString")
|
|
33
|
+
|
|
34
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(Exception,))
|
|
35
|
+
def set_secret(self, key: str, value: str) -> None:
|
|
36
|
+
from botocore.exceptions import ClientError
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
self._client.put_secret_value(SecretId=key, SecretString=value)
|
|
40
|
+
except ClientError as exc:
|
|
41
|
+
if exc.response["Error"]["Code"] == "ResourceNotFoundException":
|
|
42
|
+
self._client.create_secret(Name=key, SecretString=value)
|
|
43
|
+
else:
|
|
44
|
+
raise
|
|
45
|
+
|
|
46
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(Exception,))
|
|
47
|
+
def delete_secret(self, key: str) -> bool:
|
|
48
|
+
from botocore.exceptions import ClientError
|
|
49
|
+
|
|
50
|
+
# Real AWS raises ResourceNotFoundException for deleting a missing
|
|
51
|
+
# secret, but at least one mock (moto) returns a synthetic success
|
|
52
|
+
# response instead of raising -- checking existence explicitly
|
|
53
|
+
# first makes this return value correct regardless of what a given
|
|
54
|
+
# backend/mock does on delete of something that was never there.
|
|
55
|
+
if self.get_secret(key) is None:
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
self._client.delete_secret(SecretId=key, ForceDeleteWithoutRecovery=True)
|
|
60
|
+
return True
|
|
61
|
+
except ClientError as exc:
|
|
62
|
+
if exc.response["Error"]["Code"] == "ResourceNotFoundException":
|
|
63
|
+
return False
|
|
64
|
+
raise
|
|
65
|
+
|
|
66
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(Exception,))
|
|
67
|
+
def list_secrets(self, prefix: str = "") -> list[str]:
|
|
68
|
+
names: list[str] = []
|
|
69
|
+
paginator = self._client.get_paginator("list_secrets")
|
|
70
|
+
for page in paginator.paginate():
|
|
71
|
+
names.extend(s["Name"] for s in page["SecretList"] if s["Name"].startswith(prefix))
|
|
72
|
+
return names
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# src/polydb/adapters/AzureKeyVaultAdapter.py
|
|
2
|
+
import os
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from ..base.SecretsAdapter import SecretsAdapter
|
|
6
|
+
from ..errors import ConnectionError
|
|
7
|
+
from ..retry import retry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AzureKeyVaultAdapter(SecretsAdapter):
|
|
11
|
+
def __init__(self, vault_url: str = ""):
|
|
12
|
+
super().__init__()
|
|
13
|
+
|
|
14
|
+
self.vault_url = vault_url or os.getenv("AZURE_KEY_VAULT_URL")
|
|
15
|
+
if not self.vault_url:
|
|
16
|
+
raise ConnectionError("AZURE_KEY_VAULT_URL is not configured")
|
|
17
|
+
|
|
18
|
+
self._client = None
|
|
19
|
+
self._initialize_client()
|
|
20
|
+
|
|
21
|
+
def _initialize_client(self) -> None:
|
|
22
|
+
from azure.identity import DefaultAzureCredential
|
|
23
|
+
from azure.keyvault.secrets import SecretClient
|
|
24
|
+
|
|
25
|
+
self._client = SecretClient(vault_url=self.vault_url, credential=DefaultAzureCredential())
|
|
26
|
+
|
|
27
|
+
# Key Vault secret names may only contain alphanumerics and dashes --
|
|
28
|
+
# our secret keys (e.g. "tenant-42/stripe/api_key") use slashes, so
|
|
29
|
+
# translate to a Key-Vault-safe name deterministically both ways.
|
|
30
|
+
@staticmethod
|
|
31
|
+
def _kv_name(key: str) -> str:
|
|
32
|
+
return key.replace("/", "--")
|
|
33
|
+
|
|
34
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(Exception,))
|
|
35
|
+
def get_secret(self, key: str) -> Optional[str]:
|
|
36
|
+
from azure.core.exceptions import ResourceNotFoundError
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
return self._client.get_secret(self._kv_name(key)).value
|
|
40
|
+
except ResourceNotFoundError:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(Exception,))
|
|
44
|
+
def set_secret(self, key: str, value: str) -> None:
|
|
45
|
+
self._client.set_secret(self._kv_name(key), value)
|
|
46
|
+
|
|
47
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(Exception,))
|
|
48
|
+
def delete_secret(self, key: str) -> bool:
|
|
49
|
+
from azure.core.exceptions import ResourceNotFoundError
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
poller = self._client.begin_delete_secret(self._kv_name(key))
|
|
53
|
+
poller.wait()
|
|
54
|
+
return True
|
|
55
|
+
except ResourceNotFoundError:
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(Exception,))
|
|
59
|
+
def list_secrets(self, prefix: str = "") -> list[str]:
|
|
60
|
+
kv_prefix = self._kv_name(prefix)
|
|
61
|
+
return [
|
|
62
|
+
p.name.replace("--", "/")
|
|
63
|
+
for p in self._client.list_properties_of_secrets()
|
|
64
|
+
if p.name.startswith(kv_prefix)
|
|
65
|
+
]
|
|
@@ -116,10 +116,29 @@ class AzureQueueAdapter(QueueAdapter):
|
|
|
116
116
|
# a still-in-flight message was becoming visible again and getting
|
|
117
117
|
# picked up by a second worker mid-processing: two concurrent
|
|
118
118
|
# executions of the same task, one of which stomps/duplicates the
|
|
119
|
-
# other's result.
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
|
|
119
|
+
# other's result. ack() deletes the message immediately on completion
|
|
120
|
+
# regardless; a genuinely crashed worker recovers it after this timeout.
|
|
121
|
+
#
|
|
122
|
+
# This MUST be >= the longest a consumer may legitimately hold a message.
|
|
123
|
+
# It was 300s while the platform's durable tasks are allowed 3600s
|
|
124
|
+
# (execution/durable_job.py: timeout_seconds=3600.0), so every task that
|
|
125
|
+
# ran longer than five minutes was redelivered WHILE STILL RUNNING -- and
|
|
126
|
+
# again at ten, at fifteen, each redelivery starting another concurrent
|
|
127
|
+
# execution of the same job. Observed on an artifact generation run: the
|
|
128
|
+
# second invocation found the first's per-artifact lock held, recorded the
|
|
129
|
+
# artifact as `already_running`, and carried on to the next one, so the
|
|
130
|
+
# first artifact sat at `running` forever while its dependents blocked on
|
|
131
|
+
# it. CPU pegged, RSS climbed with every duplicate, and the process was
|
|
132
|
+
# eventually OOM-killed. The de-duplication guard upstream could not save
|
|
133
|
+
# it: a redelivery carries the SAME run id as the original, so nothing in
|
|
134
|
+
# the payload distinguishes them.
|
|
135
|
+
#
|
|
136
|
+
# Matched to the durable task timeout deliberately: the queue must not
|
|
137
|
+
# reclaim a message the executor is still allowed to be working on. The
|
|
138
|
+
# cost is that a hard worker crash now leaves the message invisible for up
|
|
139
|
+
# to an hour before recovery, which is the right trade -- a delayed retry
|
|
140
|
+
# is recoverable, two concurrent runs mutating the same rows are not.
|
|
141
|
+
DEFAULT_VISIBILITY_TIMEOUT = int(os.environ.get("POLYDB_QUEUE_VISIBILITY_TIMEOUT") or 3600)
|
|
123
142
|
|
|
124
143
|
@retry(max_attempts=3, delay=1.0, exceptions=(QueueError,))
|
|
125
144
|
def receive(
|
|
@@ -253,3 +272,62 @@ class AzureQueueAdapter(QueueAdapter):
|
|
|
253
272
|
return True
|
|
254
273
|
except Exception as e:
|
|
255
274
|
raise QueueError(f"Azure Queue nack failed: {e}")
|
|
275
|
+
|
|
276
|
+
# ---------------------------------------------------------
|
|
277
|
+
# extend / delay / cancel -- all three real on Azure Queue.
|
|
278
|
+
# ---------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
def extend(
|
|
281
|
+
self, ack_id: str, queue_name: str = "default", *, visibility_timeout: int = 30
|
|
282
|
+
) -> bool:
|
|
283
|
+
"""Real Azure UpdateMessage call extending visibility without
|
|
284
|
+
touching the stored body -- ack_id is the combined
|
|
285
|
+
"<message_id>|<pop_receipt>" receipt_handle receive() returned."""
|
|
286
|
+
message_id, pop_receipt = self._decode_receipt(ack_id)
|
|
287
|
+
if not message_id:
|
|
288
|
+
raise QueueError("AzureQueueAdapter.extend requires a receipt_handle from receive()")
|
|
289
|
+
try:
|
|
290
|
+
queue_name = self._normalize_queue_name(queue_name)
|
|
291
|
+
queue_client = self._get_queue(queue_name)
|
|
292
|
+
# No `content=` argument: this is the SDK's own documented
|
|
293
|
+
# way to leave the stored message body unchanged while only
|
|
294
|
+
# updating visibility_timeout.
|
|
295
|
+
queue_client.update_message(
|
|
296
|
+
message_id,
|
|
297
|
+
pop_receipt=pop_receipt,
|
|
298
|
+
visibility_timeout=visibility_timeout,
|
|
299
|
+
)
|
|
300
|
+
return True
|
|
301
|
+
except Exception as e:
|
|
302
|
+
raise QueueError(f"Azure Queue extend failed: {e}")
|
|
303
|
+
|
|
304
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(QueueError,))
|
|
305
|
+
def delay(
|
|
306
|
+
self, message: Dict[str, Any], queue_name: str = "default", *, delay_seconds: int = 0
|
|
307
|
+
) -> str:
|
|
308
|
+
"""Real Azure send_message with its own visibility_timeout param
|
|
309
|
+
-- the message is stored immediately but not deliverable until
|
|
310
|
+
delay_seconds elapses. Returns the combined receipt (packed via
|
|
311
|
+
_encode_receipt) so cancel() has enough to delete it before then."""
|
|
312
|
+
try:
|
|
313
|
+
queue_name = self._normalize_queue_name(queue_name)
|
|
314
|
+
queue_client = self._get_queue(queue_name)
|
|
315
|
+
|
|
316
|
+
response = queue_client.send_message(
|
|
317
|
+
json.dumps(message, default=json_safe),
|
|
318
|
+
visibility_timeout=delay_seconds,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
return self._encode_receipt(response.id, response.pop_receipt)
|
|
322
|
+
|
|
323
|
+
except Exception as e:
|
|
324
|
+
raise QueueError(f"Azure Queue delay failed: {e}")
|
|
325
|
+
|
|
326
|
+
def cancel(self, message_id: str, queue_name: str = "default") -> bool:
|
|
327
|
+
"""Cancel a still-delayed message before its visibility_timeout
|
|
328
|
+
elapses. message_id is the combined receipt delay() returned."""
|
|
329
|
+
decoded_id, pop_receipt = self._decode_receipt(message_id)
|
|
330
|
+
if not decoded_id:
|
|
331
|
+
raise QueueError("AzureQueueAdapter.cancel requires the receipt delay() returned")
|
|
332
|
+
queue_name = self._normalize_queue_name(queue_name)
|
|
333
|
+
return self.delete(message_id=decoded_id, queue_name=queue_name, pop_receipt=pop_receipt)
|
|
@@ -23,10 +23,12 @@ class DynamoDBAdapter(NoSQLKVAdapter):
|
|
|
23
23
|
Production-grade DynamoDB adapter with optional S3 overflow.
|
|
24
24
|
|
|
25
25
|
Goals (matches your adapter test style)
|
|
26
|
-
- stored row keeps "id" ==
|
|
27
|
-
|
|
26
|
+
- stored row keeps "id" == rk (the row key -- see NoSQLKVAdapter._get_pk_rk,
|
|
27
|
+
which derives rk from data.get(rk_field, ...) with rk_field defaulting
|
|
28
|
+
to "id"); put() returns the full stored record, not just {"id": ...}
|
|
29
|
+
- query({"id": ...}) works (matches the record's own "id"/rk, not PK)
|
|
28
30
|
- patch() merges (handled by NoSQLKVAdapter.patch)
|
|
29
|
-
- delete() returns {"id": <
|
|
31
|
+
- delete() returns {"id": <rk>} and raises sqlite3.DatabaseError on missing
|
|
30
32
|
- query_page() uses DynamoDB LastEvaluatedKey token (stable)
|
|
31
33
|
- LocalStack support (endpoint_url) + auto create table/bucket in test/dev
|
|
32
34
|
"""
|
|
@@ -273,16 +275,24 @@ class DynamoDBAdapter(NoSQLKVAdapter):
|
|
|
273
275
|
payload["PK"] = pk
|
|
274
276
|
payload["SK"] = rk
|
|
275
277
|
|
|
276
|
-
#
|
|
277
|
-
|
|
278
|
+
# "id" is the row key (NoSQLKVAdapter._get_pk_rk derives `rk` from
|
|
279
|
+
# data.get(rk_field, ...) with rk_field defaulting to "id"), so it
|
|
280
|
+
# is already present in `payload` via dict(data) above whenever the
|
|
281
|
+
# caller supplied one. Only fall back to `rk` -- never `pk`, which
|
|
282
|
+
# would silently collide every row in the same partition onto the
|
|
283
|
+
# same "id" and break id-addressed lookups (query/get/patch/delete
|
|
284
|
+
# by {"id": ...}), the same corruption previously present here and
|
|
285
|
+
# in VercelKVAdapter.
|
|
286
|
+
payload.setdefault("id", rk)
|
|
278
287
|
payload.setdefault("_pk", pk)
|
|
279
288
|
payload.setdefault("_rk", rk)
|
|
280
289
|
|
|
281
290
|
ref = self._maybe_overflow_to_s3(model, pk, rk, payload)
|
|
282
291
|
table.put_item(Item=ref if ref is not None else payload)
|
|
283
292
|
|
|
284
|
-
#
|
|
285
|
-
|
|
293
|
+
# Return the full stored record, not just {"id": pk} -- callers
|
|
294
|
+
# (e.g. DatabaseFactory.create()) use this as the created row.
|
|
295
|
+
return payload
|
|
286
296
|
|
|
287
297
|
except Exception as e:
|
|
288
298
|
raise NoSQLError(f"DynamoDB put failed: {e}")
|
|
@@ -297,7 +307,7 @@ class DynamoDBAdapter(NoSQLKVAdapter):
|
|
|
297
307
|
if not item:
|
|
298
308
|
return None
|
|
299
309
|
|
|
300
|
-
item.setdefault("id",
|
|
310
|
+
item.setdefault("id", rk)
|
|
301
311
|
return self._resolve_overflow(item)
|
|
302
312
|
|
|
303
313
|
except Exception as e:
|
|
@@ -318,8 +328,11 @@ class DynamoDBAdapter(NoSQLKVAdapter):
|
|
|
318
328
|
table = self._get_table(model)
|
|
319
329
|
filters = filters or {}
|
|
320
330
|
|
|
321
|
-
#
|
|
322
|
-
|
|
331
|
+
# "id" is the row key by convention (see _put_raw), not the
|
|
332
|
+
# partition key -- only route to Query when the caller names the
|
|
333
|
+
# actual PK explicitly. A bare {"id": ...} filter falls through to
|
|
334
|
+
# Scan below with "id" applied as a normal attribute filter.
|
|
335
|
+
pk_value = filters.get("PK") or filters.get("partition_key")
|
|
323
336
|
|
|
324
337
|
if pk_value is not None:
|
|
325
338
|
key_cond = Key("PK").eq(str(pk_value))
|
|
@@ -330,7 +343,7 @@ class DynamoDBAdapter(NoSQLKVAdapter):
|
|
|
330
343
|
|
|
331
344
|
# Other filters as FilterExpression
|
|
332
345
|
other = {
|
|
333
|
-
k: v for k, v in filters.items() if k not in ("PK", "SK", "partition_key"
|
|
346
|
+
k: v for k, v in filters.items() if k not in ("PK", "SK", "partition_key")
|
|
334
347
|
}
|
|
335
348
|
if other:
|
|
336
349
|
expr = None
|
|
@@ -361,7 +374,7 @@ class DynamoDBAdapter(NoSQLKVAdapter):
|
|
|
361
374
|
|
|
362
375
|
out: List[JsonDict] = []
|
|
363
376
|
for it in items:
|
|
364
|
-
it.setdefault("id", it.get("
|
|
377
|
+
it.setdefault("id", it.get("_rk") or it.get("SK"))
|
|
365
378
|
out.append(self._resolve_overflow(it))
|
|
366
379
|
return out
|
|
367
380
|
|
|
@@ -392,7 +405,7 @@ class DynamoDBAdapter(NoSQLKVAdapter):
|
|
|
392
405
|
pass
|
|
393
406
|
|
|
394
407
|
table.delete_item(Key={"PK": pk, "SK": rk})
|
|
395
|
-
return {"id":
|
|
408
|
+
return {"id": rk}
|
|
396
409
|
|
|
397
410
|
except DatabaseError:
|
|
398
411
|
raise
|
|
@@ -427,7 +440,9 @@ class DynamoDBAdapter(NoSQLKVAdapter):
|
|
|
427
440
|
|
|
428
441
|
start_key = self._decode_token(continuation_token) if continuation_token else None
|
|
429
442
|
|
|
430
|
-
|
|
443
|
+
# "id" is the row key by convention, not the partition key --
|
|
444
|
+
# see _query_raw.
|
|
445
|
+
pk_value = query.get("PK") or query.get("partition_key")
|
|
431
446
|
|
|
432
447
|
items: List[JsonDict] = []
|
|
433
448
|
last_key = start_key
|
|
@@ -448,7 +463,7 @@ class DynamoDBAdapter(NoSQLKVAdapter):
|
|
|
448
463
|
kwargs["ExclusiveStartKey"] = last_key
|
|
449
464
|
|
|
450
465
|
other_filters = {
|
|
451
|
-
k: v for k, v in query.items() if k not in ("PK", "SK", "partition_key"
|
|
466
|
+
k: v for k, v in query.items() if k not in ("PK", "SK", "partition_key")
|
|
452
467
|
}
|
|
453
468
|
|
|
454
469
|
if other_filters:
|
|
@@ -501,7 +516,7 @@ class DynamoDBAdapter(NoSQLKVAdapter):
|
|
|
501
516
|
out: List[JsonDict] = []
|
|
502
517
|
|
|
503
518
|
for it in items:
|
|
504
|
-
it.setdefault("id", it.get("
|
|
519
|
+
it.setdefault("id", it.get("_rk") or it.get("SK"))
|
|
505
520
|
out.append(self._resolve_overflow(it))
|
|
506
521
|
|
|
507
522
|
next_tok = self._encode_token(last_key) if last_key else None
|