altcodepro-polydb-python 2.5.20__tar.gz → 2.5.21__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {altcodepro_polydb_python-2.5.20/src/altcodepro_polydb_python.egg-info → altcodepro_polydb_python-2.5.21}/PKG-INFO +1 -1
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/pyproject.toml +1 -1
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21/src/altcodepro_polydb_python.egg-info}/PKG-INFO +1 -1
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/altcodepro_polydb_python.egg-info/SOURCES.txt +2 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/AzureKeyVaultAdapter.py +16 -2
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/AzureTableStorageAdapter.py +61 -4
- altcodepro_polydb_python-2.5.21/tests/test_azure_keyvault_credential.py +34 -0
- altcodepro_polydb_python-2.5.21/tests/test_azure_table_self_heal.py +64 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/LICENSE +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/MANIFEST.in +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/README.md +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/requirements-aws.txt +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/requirements-azure.txt +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/requirements-dev.txt +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/requirements-gcp.txt +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/requirements-generic.txt +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/requirements.txt +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/setup.cfg +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/setup.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/altcodepro_polydb_python.egg-info/dependency_links.txt +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/altcodepro_polydb_python.egg-info/requires.txt +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/altcodepro_polydb_python.egg-info/top_level.txt +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/PolyDB.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/__init__.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/AWSSecretsManagerAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/AzureBlobStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/AzureFileStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/AzureQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/BlockchainBlobAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/BlockchainFileAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/BlockchainKVAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/BlockchainQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/DynamoDBAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/EFSAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/FirestoreAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/GCPFilestoreAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/GCPPubSubAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/GCPSecretManagerAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/GCPStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/KafkaQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/MongoDBAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/PostgreSQLAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/RabbitMQAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/S3Adapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/S3CompatibleAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/SQSAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/VaultAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/VercelBlobAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/VercelFileAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/VercelKVAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/VercelQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/__init__.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/advanced_query.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/aio.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/audit/AuditStorage.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/audit/__init__.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/audit/context.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/audit/manager.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/audit/models.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/base/NoSQLKVAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/base/ObjectStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/base/QueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/base/SecretsAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/base/SharedFilesAdapter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/base/__init__.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/batch.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/cache.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/cloudDatabaseFactory.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/databaseFactory.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/errors.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/json_safe.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/models.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/monitoring.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/multitenancy.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/observability/__init__.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/observability/logging.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/overflow_gc.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/py.typed +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/query.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/registry.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/retry.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/schema.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/security.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/services/__init__.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/services/compliance_service.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/services/security_service.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/types.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/utils.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/validation.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_advanced_query_injection.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_async_api.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_atomic_decrement.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_audit_chain_cutover.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_audit_hmac.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_aws.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_azure.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_azure_queue_receipt_handle.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_azure_table_empty_filter.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_azure_table_id_property_persisted.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_azure_table_id_return_value_sanitization_mismatch.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_azure_table_large_record_patch_roundtrip.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_azure_table_unpack_entity.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_blockchain.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_blockchain_overflow_guard.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_cloud_factory.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_factory_wiring.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_field_encryption.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_gcp.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_kafka.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_like_pattern_escaping.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_mongodb.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_monitoring_health.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_multi_engine.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_multitenancy_ddl_injection.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_nosql_kv_overflow_write_path.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_nosql_operator_injection.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_observability.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_overflow_gc.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_postgresql.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_postgresql_deserialize_row.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_queue_extend_delay_cancel.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_rabbitmq.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_read_projection.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_redis_kv.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_schema_ddl_injection.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_secrets_aws.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_secrets_vault.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_session_vars.py +0 -0
- {altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_vercel.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: altcodepro-polydb-python
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.21
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "altcodepro-polydb-python"
|
|
7
|
-
version = "2.5.
|
|
7
|
+
version = "2.5.21"
|
|
8
8
|
description = "Production-ready multi-cloud database abstraction layer with connection pooling, retry logic, and thread safety"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: altcodepro-polydb-python
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.21
|
|
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
|
|
@@ -90,11 +90,13 @@ tests/test_audit_chain_cutover.py
|
|
|
90
90
|
tests/test_audit_hmac.py
|
|
91
91
|
tests/test_aws.py
|
|
92
92
|
tests/test_azure.py
|
|
93
|
+
tests/test_azure_keyvault_credential.py
|
|
93
94
|
tests/test_azure_queue_receipt_handle.py
|
|
94
95
|
tests/test_azure_table_empty_filter.py
|
|
95
96
|
tests/test_azure_table_id_property_persisted.py
|
|
96
97
|
tests/test_azure_table_id_return_value_sanitization_mismatch.py
|
|
97
98
|
tests/test_azure_table_large_record_patch_roundtrip.py
|
|
99
|
+
tests/test_azure_table_self_heal.py
|
|
98
100
|
tests/test_azure_table_unpack_entity.py
|
|
99
101
|
tests/test_blockchain.py
|
|
100
102
|
tests/test_blockchain_overflow_guard.py
|
|
@@ -19,10 +19,24 @@ class AzureKeyVaultAdapter(SecretsAdapter):
|
|
|
19
19
|
self._initialize_client()
|
|
20
20
|
|
|
21
21
|
def _initialize_client(self) -> None:
|
|
22
|
-
from azure.identity import DefaultAzureCredential
|
|
23
22
|
from azure.keyvault.secrets import SecretClient
|
|
24
23
|
|
|
25
|
-
self._client = SecretClient(vault_url=self.vault_url, credential=
|
|
24
|
+
self._client = SecretClient(vault_url=self.vault_url, credential=self._build_credential())
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def _build_credential():
|
|
28
|
+
"""Inside Azure Container Apps / App Service / Functions a managed identity is available
|
|
29
|
+
(IDENTITY_ENDPOINT / MSI_ENDPOINT). DefaultAzureCredential tries EnvironmentCredential FIRST, and a deployment that
|
|
30
|
+
also exports AZURE_CLIENT_ID + AZURE_CLIENT_SECRET (for some other purpose) then fails with AADSTS7000232
|
|
31
|
+
"MSI identity should not use ClientSecretCredential". So when a managed identity exists it goes first
|
|
32
|
+
(AZURE_CLIENT_ID, if set, names a user-assigned identity); the normal chain stays as the fallback, so local
|
|
33
|
+
development (az login, environment variables) behaves as before."""
|
|
34
|
+
from azure.identity import ChainedTokenCredential, DefaultAzureCredential, ManagedIdentityCredential
|
|
35
|
+
|
|
36
|
+
if os.getenv("IDENTITY_ENDPOINT") or os.getenv("MSI_ENDPOINT"):
|
|
37
|
+
client_id = os.getenv("AZURE_CLIENT_ID") or None
|
|
38
|
+
return ChainedTokenCredential(ManagedIdentityCredential(client_id=client_id), DefaultAzureCredential())
|
|
39
|
+
return DefaultAzureCredential()
|
|
26
40
|
|
|
27
41
|
# Key Vault secret names may only contain alphanumerics and dashes --
|
|
28
42
|
# our secret keys (e.g. "tenant-42/stripe/api_key") use slashes, so
|
|
@@ -47,6 +47,59 @@ _RETRY_TOTAL = int(os.getenv("AZURE_TABLE_RETRY_TOTAL", "3"))
|
|
|
47
47
|
logging.getLogger("azure").setLevel(logging.ERROR)
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
|
|
51
|
+
def _is_table_missing(exc: BaseException) -> bool:
|
|
52
|
+
text = f"{type(exc).__name__} {exc}"
|
|
53
|
+
return "TableNotFound" in text or "The table specified does not exist" in text
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class _SelfHealingTableClient:
|
|
57
|
+
"""Wraps an Azure TableClient so a table that disappears while the process is running (deleted by hand, a data
|
|
58
|
+
reset, a restore) is created again and the operation retried once, instead of failing with TableNotFound until
|
|
59
|
+
the process restarts. Lazy iterators (query_entities / list_entities) are healed only if the failure happens
|
|
60
|
+
before the first row was produced."""
|
|
61
|
+
|
|
62
|
+
_LAZY = ("query_entities", "list_entities")
|
|
63
|
+
|
|
64
|
+
def __init__(self, adapter: "AzureTableStorageAdapter", table_name: str, client: Any):
|
|
65
|
+
self._adapter = adapter
|
|
66
|
+
self._table_name = table_name
|
|
67
|
+
self._client = client
|
|
68
|
+
|
|
69
|
+
def _heal(self) -> None:
|
|
70
|
+
self._adapter._ensured_tables.discard(self._table_name)
|
|
71
|
+
self._adapter._client.create_table_if_not_exists(self._table_name)
|
|
72
|
+
self._adapter._ensured_tables.add(self._table_name)
|
|
73
|
+
logger.warning(f"Azure Table {self._table_name} was missing; created it again")
|
|
74
|
+
|
|
75
|
+
def __getattr__(self, name: str):
|
|
76
|
+
target = getattr(self._client, name)
|
|
77
|
+
if not callable(target):
|
|
78
|
+
return target
|
|
79
|
+
if name in self._LAZY:
|
|
80
|
+
def lazy(*args, **kwargs):
|
|
81
|
+
produced = False
|
|
82
|
+
try:
|
|
83
|
+
for item in target(*args, **kwargs):
|
|
84
|
+
produced = True
|
|
85
|
+
yield item
|
|
86
|
+
except Exception as exc: # noqa: BLE001
|
|
87
|
+
if produced or not _is_table_missing(exc):
|
|
88
|
+
raise
|
|
89
|
+
self._heal()
|
|
90
|
+
yield from getattr(self._client, name)(*args, **kwargs)
|
|
91
|
+
return lazy
|
|
92
|
+
|
|
93
|
+
def call(*args, **kwargs):
|
|
94
|
+
try:
|
|
95
|
+
return target(*args, **kwargs)
|
|
96
|
+
except Exception as exc: # noqa: BLE001
|
|
97
|
+
if not _is_table_missing(exc):
|
|
98
|
+
raise
|
|
99
|
+
self._heal()
|
|
100
|
+
return getattr(self._client, name)(*args, **kwargs)
|
|
101
|
+
return call
|
|
102
|
+
|
|
50
103
|
class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
51
104
|
"""
|
|
52
105
|
Azure Table Storage adapter with:
|
|
@@ -441,17 +494,21 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
441
494
|
try:
|
|
442
495
|
self._client.create_table_if_not_exists(table_name)
|
|
443
496
|
logger.info(f"✅ Azure Table ensured/created: {table_name}")
|
|
497
|
+
self._ensured_tables.add(table_name)
|
|
444
498
|
except Exception as e:
|
|
445
499
|
msg = str(e)
|
|
446
|
-
if "TableAlreadyExists"
|
|
447
|
-
|
|
448
|
-
|
|
500
|
+
if "TableAlreadyExists" in msg or "already exists" in msg.lower():
|
|
501
|
+
self._ensured_tables.add(table_name)
|
|
502
|
+
else:
|
|
503
|
+
# Do NOT remember a failed creation as "ensured": the next call tries again instead of
|
|
504
|
+
# failing with TableNotFound for the rest of the process's life.
|
|
505
|
+
logger.error(f"Could not create table {table_name}: {e}")
|
|
449
506
|
|
|
450
507
|
if cached is None:
|
|
451
508
|
cached = self._client.get_table_client(table_name=table_name)
|
|
452
509
|
self._table_clients_cache[table_name] = cached
|
|
453
510
|
|
|
454
|
-
return cached
|
|
511
|
+
return _SelfHealingTableClient(self, table_name, cached)
|
|
455
512
|
|
|
456
513
|
def _restore_overflow_properties(self, entity_dict: JsonDict) -> JsonDict:
|
|
457
514
|
restored = {}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Inside a managed-identity host the managed identity is tried first, so a stray AZURE_CLIENT_SECRET cannot break Key Vault."""
|
|
2
|
+
import sys
|
|
3
|
+
import types
|
|
4
|
+
from unittest.mock import MagicMock
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def fake_identity(monkeypatch):
|
|
11
|
+
mod = types.ModuleType("azure.identity")
|
|
12
|
+
mod.ChainedTokenCredential = MagicMock(name="Chained")
|
|
13
|
+
mod.DefaultAzureCredential = MagicMock(name="Default")
|
|
14
|
+
mod.ManagedIdentityCredential = MagicMock(name="MI")
|
|
15
|
+
monkeypatch.setitem(sys.modules, "azure.identity", mod)
|
|
16
|
+
return mod
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_a_managed_identity_host_puts_the_managed_identity_first(fake_identity, monkeypatch):
|
|
20
|
+
from polydb.adapters.AzureKeyVaultAdapter import AzureKeyVaultAdapter
|
|
21
|
+
monkeypatch.setenv("IDENTITY_ENDPOINT", "http://169.254.0.1/msi")
|
|
22
|
+
monkeypatch.setenv("AZURE_CLIENT_ID", "mi-client-id")
|
|
23
|
+
AzureKeyVaultAdapter._build_credential()
|
|
24
|
+
fake_identity.ManagedIdentityCredential.assert_called_once_with(client_id="mi-client-id")
|
|
25
|
+
chain_args = fake_identity.ChainedTokenCredential.call_args.args
|
|
26
|
+
assert chain_args[0] is fake_identity.ManagedIdentityCredential.return_value and chain_args[1] is fake_identity.DefaultAzureCredential.return_value
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_elsewhere_the_default_chain_is_unchanged(fake_identity, monkeypatch):
|
|
30
|
+
from polydb.adapters.AzureKeyVaultAdapter import AzureKeyVaultAdapter
|
|
31
|
+
monkeypatch.delenv("IDENTITY_ENDPOINT", raising=False)
|
|
32
|
+
monkeypatch.delenv("MSI_ENDPOINT", raising=False)
|
|
33
|
+
assert AzureKeyVaultAdapter._build_credential() is fake_identity.DefaultAzureCredential.return_value
|
|
34
|
+
fake_identity.ManagedIdentityCredential.assert_not_called()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""A missing Azure Table is created again and the call retried; a failed creation is not remembered as success."""
|
|
2
|
+
from unittest.mock import MagicMock
|
|
3
|
+
|
|
4
|
+
from polydb.adapters.AzureTableStorageAdapter import _SelfHealingTableClient, _is_table_missing
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class _Err(Exception):
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _adapter():
|
|
12
|
+
a = MagicMock()
|
|
13
|
+
a._ensured_tables = {"t"}
|
|
14
|
+
return a
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_detects_the_missing_table_error():
|
|
18
|
+
assert _is_table_missing(_Err("ErrorCode:TableNotFound"))
|
|
19
|
+
assert _is_table_missing(_Err("The table specified does not exist."))
|
|
20
|
+
assert not _is_table_missing(_Err("timeout"))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_a_call_on_a_missing_table_creates_it_and_retries_once():
|
|
24
|
+
client = MagicMock()
|
|
25
|
+
client.get_entity.side_effect = [_Err("TableNotFound"), {"ok": 1}]
|
|
26
|
+
a = _adapter()
|
|
27
|
+
got = _SelfHealingTableClient(a, "t", client).get_entity(partition_key="p", row_key="r")
|
|
28
|
+
assert got == {"ok": 1} and a._client.create_table_if_not_exists.called and "t" in a._ensured_tables
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_other_errors_are_not_swallowed():
|
|
32
|
+
client = MagicMock()
|
|
33
|
+
client.get_entity.side_effect = _Err("boom")
|
|
34
|
+
try:
|
|
35
|
+
_SelfHealingTableClient(_adapter(), "t", client).get_entity()
|
|
36
|
+
raise AssertionError("should raise")
|
|
37
|
+
except _Err:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_a_lazy_query_on_a_missing_table_heals_before_the_first_row():
|
|
42
|
+
client = MagicMock()
|
|
43
|
+
def gone():
|
|
44
|
+
raise _Err("TableNotFound")
|
|
45
|
+
yield
|
|
46
|
+
client.query_entities.side_effect = [gone(), iter([{"a": 1}, {"a": 2}])]
|
|
47
|
+
a = _adapter()
|
|
48
|
+
rows = list(_SelfHealingTableClient(a, "t", client).query_entities("x"))
|
|
49
|
+
assert rows == [{"a": 1}, {"a": 2}] and a._client.create_table_if_not_exists.called
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_a_failure_after_rows_were_produced_is_raised_not_replayed():
|
|
53
|
+
client = MagicMock()
|
|
54
|
+
def partial():
|
|
55
|
+
yield {"a": 1}
|
|
56
|
+
raise _Err("TableNotFound")
|
|
57
|
+
client.query_entities.return_value = partial()
|
|
58
|
+
it = _SelfHealingTableClient(_adapter(), "t", client).query_entities("x")
|
|
59
|
+
assert next(it) == {"a": 1}
|
|
60
|
+
try:
|
|
61
|
+
next(it)
|
|
62
|
+
raise AssertionError("should raise")
|
|
63
|
+
except _Err:
|
|
64
|
+
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/requirements-generic.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/S3Adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/adapters/__init__.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/advanced_query.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/audit/AuditStorage.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/audit/__init__.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/audit/context.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/audit/manager.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/audit/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/base/QueueAdapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/base/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/databaseFactory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/monitoring.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/multitenancy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/overflow_gc.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/services/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/src/polydb/validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_atomic_decrement.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_audit_hmac.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_blockchain.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_cloud_factory.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_factory_wiring.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_field_encryption.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_monitoring_health.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_multi_engine.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_observability.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_overflow_gc.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_postgresql.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_read_projection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_secrets_aws.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_secrets_vault.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.5.20 → altcodepro_polydb_python-2.5.21}/tests/test_session_vars.py
RENAMED
|
File without changes
|
|
File without changes
|