altcodepro-polydb-python 2.5.3__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.3.dist-info → altcodepro_polydb_python-2.5.6.dist-info}/METADATA +69 -20
- {altcodepro_polydb_python-2.5.3.dist-info → altcodepro_polydb_python-2.5.6.dist-info}/RECORD +29 -22
- 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/AzureTableStorageAdapter.py +32 -5
- 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.3.dist-info → altcodepro_polydb_python-2.5.6.dist-info}/WHEEL +0 -0
- {altcodepro_polydb_python-2.5.3.dist-info → altcodepro_polydb_python-2.5.6.dist-info}/licenses/LICENSE +0 -0
- {altcodepro_polydb_python-2.5.3.dist-info → altcodepro_polydb_python-2.5.6.dist-info}/top_level.txt +0 -0
{altcodepro_polydb_python-2.5.3.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.3.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/
|
|
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=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)
|
|
@@ -24,6 +24,16 @@ from ..models import PageResult, PartitionConfig
|
|
|
24
24
|
|
|
25
25
|
logger = logging.getLogger(__name__)
|
|
26
26
|
|
|
27
|
+
# Imported at module scope (unlike the clients, which stay lazy) because these
|
|
28
|
+
# are pure value types with no connection cost, and both the encode and decode
|
|
29
|
+
# paths need them per field. Guarded so importing polydb without the azure
|
|
30
|
+
# extra still works -- in that state nothing in this adapter functions anyway.
|
|
31
|
+
try:
|
|
32
|
+
from azure.data.tables import EdmType, EntityProperty
|
|
33
|
+
except ImportError: # pragma: no cover - azure extra not installed
|
|
34
|
+
EdmType = None # type: ignore[assignment]
|
|
35
|
+
EntityProperty = None # type: ignore[assignment]
|
|
36
|
+
|
|
27
37
|
_BYTES_PREFIX = "@@polydb_bytes@@:"
|
|
28
38
|
_JSON_PREFIX = "@@polydb_json@@:"
|
|
29
39
|
_BASE64_RE = re.compile(r"^[A-Za-z0-9+/]*={0,2}$")
|
|
@@ -155,15 +165,25 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
155
165
|
|
|
156
166
|
if isinstance(v, datetime):
|
|
157
167
|
return v
|
|
158
|
-
if isinstance(v, int):
|
|
168
|
+
if isinstance(v, int) and not isinstance(v, bool):
|
|
159
169
|
# Azure Table Int32 range: -2_147_483_648 … 2_147_483_647
|
|
160
170
|
if -2_147_483_648 <= v <= 2_147_483_647:
|
|
161
171
|
return v
|
|
162
|
-
# Outside Int32 →
|
|
163
|
-
#
|
|
164
|
-
#
|
|
172
|
+
# Outside Int32 → must be tagged Edm.Int64 EXPLICITLY.
|
|
173
|
+
#
|
|
174
|
+
# Returning the bare int here does NOT work, despite reading like
|
|
175
|
+
# it should: the SDK's _add_entity_properties dispatches on
|
|
176
|
+
# type(value), and every Python int maps to _to_entity_int32,
|
|
177
|
+
# which raises "<n> is too large to be cast to type EdmType.INT32"
|
|
178
|
+
# for anything >= 2**31. It never infers Int64. The one documented
|
|
179
|
+
# way to reach Edm.Int64 is an EntityProperty tuple -- the
|
|
180
|
+
# serializer branches on isinstance(value, tuple) and takes the
|
|
181
|
+
# edm_type from element 1 -- so that is what we hand it.
|
|
182
|
+
# _decode_value unwraps the same shape on the way back out.
|
|
165
183
|
if -9_223_372_036_854_775_808 <= v <= 9_223_372_036_854_775_807:
|
|
166
|
-
|
|
184
|
+
if EntityProperty is not None and EdmType is not None:
|
|
185
|
+
return EntityProperty(v, EdmType.INT64)
|
|
186
|
+
return str(v)
|
|
167
187
|
return str(v) # safety net for arbitrarily large ints
|
|
168
188
|
if isinstance(v, (str, bool, int, float)):
|
|
169
189
|
return v
|
|
@@ -171,6 +191,13 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
171
191
|
return _JSON_PREFIX + json.dumps(v, default=json_safe)
|
|
172
192
|
|
|
173
193
|
def _decode_value(self, v: Any) -> Any:
|
|
194
|
+
# Edm.Int64 comes back as EntityProperty(value, EdmType.INT64), not as
|
|
195
|
+
# a plain int -- the SDK's _from_entity_int64 wraps it. Unwrap here so
|
|
196
|
+
# a large int round-trips as the int that was written; left alone it
|
|
197
|
+
# would surface to callers as a 2-tuple and, on the next write, be
|
|
198
|
+
# JSON-encoded by the list/tuple branch of _encode_value.
|
|
199
|
+
if EntityProperty is not None and isinstance(v, EntityProperty):
|
|
200
|
+
return v.value
|
|
174
201
|
if isinstance(v, str):
|
|
175
202
|
if v.startswith(_JSON_PREFIX):
|
|
176
203
|
payload = v[len(_JSON_PREFIX) :]
|