altcodepro-polydb-python 2.5.20__py3-none-any.whl → 2.5.21__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.20.dist-info → altcodepro_polydb_python-2.5.21.dist-info}/METADATA +1 -1
- {altcodepro_polydb_python-2.5.20.dist-info → altcodepro_polydb_python-2.5.21.dist-info}/RECORD +7 -7
- polydb/adapters/AzureKeyVaultAdapter.py +16 -2
- polydb/adapters/AzureTableStorageAdapter.py +61 -4
- {altcodepro_polydb_python-2.5.20.dist-info → altcodepro_polydb_python-2.5.21.dist-info}/WHEEL +0 -0
- {altcodepro_polydb_python-2.5.20.dist-info → altcodepro_polydb_python-2.5.21.dist-info}/licenses/LICENSE +0 -0
- {altcodepro_polydb_python-2.5.20.dist-info → altcodepro_polydb_python-2.5.21.dist-info}/top_level.txt +0 -0
{altcodepro_polydb_python-2.5.20.dist-info → altcodepro_polydb_python-2.5.21.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.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
|
{altcodepro_polydb_python-2.5.20.dist-info → altcodepro_polydb_python-2.5.21.dist-info}/RECORD
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
altcodepro_polydb_python-2.5.
|
|
1
|
+
altcodepro_polydb_python-2.5.21.dist-info/licenses/LICENSE,sha256=9X8GLocsBwy-5aR5JGOt2SAMDDPs9Qv-YnqmHBHOXrw,1067
|
|
2
2
|
polydb/PolyDB.py,sha256=Lp0QtG7D89Z6P6nt9fVoT8rVHAy8xlE2kFRSS70r-mk,23672
|
|
3
3
|
polydb/__init__.py,sha256=fNrKwNBgwMJKrBhs_eaRu4XpQ3olREipIOiwBmv38b8,1876
|
|
4
4
|
polydb/advanced_query.py,sha256=M9sKmdF2PsLZWSc-zKpXRcwaDumbYYTsibmL_IAjKP4,6931
|
|
@@ -25,9 +25,9 @@ polydb/validation.py,sha256=DK9-AFndTr0cLodpc3KX0wnABDpw2vPB4H6hp67BJKE,4570
|
|
|
25
25
|
polydb/adapters/AWSSecretsManagerAdapter.py,sha256=W4B3xhQFcBAlNlwQ4uTgcDmRrOmZr6v-suC4qihTpJw,2747
|
|
26
26
|
polydb/adapters/AzureBlobStorageAdapter.py,sha256=ZTovSC_CVduGnIoIQUUeNxDQlf-IbFDBRzjGDk8U9Io,7084
|
|
27
27
|
polydb/adapters/AzureFileStorageAdapter.py,sha256=VZNprqlBXCuWUgtqClNT-NrQmRf-XFYEiRA2BLbf-Sc,7046
|
|
28
|
-
polydb/adapters/AzureKeyVaultAdapter.py,sha256=
|
|
28
|
+
polydb/adapters/AzureKeyVaultAdapter.py,sha256=EtsrxI3X75nORnO0KbvKTVXJfliHd-gt3y9Mo8Npugs,3365
|
|
29
29
|
polydb/adapters/AzureQueueAdapter.py,sha256=1e2HiFvkH1BI_JKS9fgays2J41kOfH6pUPbZDe1wmWA,13658
|
|
30
|
-
polydb/adapters/AzureTableStorageAdapter.py,sha256=
|
|
30
|
+
polydb/adapters/AzureTableStorageAdapter.py,sha256=oW80ZeCsj8hkIlmlrv404rsB8m5TA9_nHL5OM57WQSg,38068
|
|
31
31
|
polydb/adapters/BlockchainBlobAdapter.py,sha256=85H3PxiL9rGDtK-sTsW26jOuUQ_SGipLSPu3s-juZRA,3298
|
|
32
32
|
polydb/adapters/BlockchainFileAdapter.py,sha256=lZbRyC18LghZcVh5QyIRP7tCftNF_Ep1FKG0KRkvMmk,8335
|
|
33
33
|
polydb/adapters/BlockchainKVAdapter.py,sha256=pvpiPshDE5Z_IRcSBvBwgAXT--86lgEyIsEUeqcmoE8,5422
|
|
@@ -68,7 +68,7 @@ polydb/observability/logging.py,sha256=FWYky-Mg1IhLpwuaKuLIsTROBZc9bI_-AO-y53nvE
|
|
|
68
68
|
polydb/services/__init__.py,sha256=Zs3PZbHutgPdPjJatygvtYvKPZMBt1qKSqESbFRPHJs,241
|
|
69
69
|
polydb/services/compliance_service.py,sha256=UMjCbQ4z13-w6pT4RLQpMi_u1zbShEpiz8kawA2WsSU,5075
|
|
70
70
|
polydb/services/security_service.py,sha256=gGo_j9Ca_rgvxS7s6C5JUktugQBj1PV1DXpMnOYAGbI,4992
|
|
71
|
-
altcodepro_polydb_python-2.5.
|
|
72
|
-
altcodepro_polydb_python-2.5.
|
|
73
|
-
altcodepro_polydb_python-2.5.
|
|
74
|
-
altcodepro_polydb_python-2.5.
|
|
71
|
+
altcodepro_polydb_python-2.5.21.dist-info/METADATA,sha256=SbnlRNiCE6ZiK4aVCr5-Znz4R4xUJpDQyh10PQYpO34,15599
|
|
72
|
+
altcodepro_polydb_python-2.5.21.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
73
|
+
altcodepro_polydb_python-2.5.21.dist-info/top_level.txt,sha256=WgLFWJoYjUhwvyPxJFl6jYLrVFuBJDX3OABf4ocwk_E,7
|
|
74
|
+
altcodepro_polydb_python-2.5.21.dist-info/RECORD,,
|
|
@@ -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 = {}
|
{altcodepro_polydb_python-2.5.20.dist-info → altcodepro_polydb_python-2.5.21.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|