altcodepro-polydb-python 2.3.24__tar.gz → 2.3.26__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.3.24/src/altcodepro_polydb_python.egg-info → altcodepro_polydb_python-2.3.26}/PKG-INFO +1 -1
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/pyproject.toml +1 -1
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26/src/altcodepro_polydb_python.egg-info}/PKG-INFO +1 -1
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/altcodepro_polydb_python.egg-info/SOURCES.txt +1 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/__init__.py +3 -1
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/AzureTableStorageAdapter.py +16 -1
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/models.py +35 -0
- altcodepro_polydb_python-2.3.26/src/polydb/utils.py +94 -0
- altcodepro_polydb_python-2.3.26/tests/test_azure_table_empty_filter.py +70 -0
- altcodepro_polydb_python-2.3.24/src/polydb/utils.py +0 -61
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/LICENSE +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/MANIFEST.in +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/README.md +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/requirements-aws.txt +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/requirements-azure.txt +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/requirements-dev.txt +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/requirements-gcp.txt +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/requirements-generic.txt +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/requirements.txt +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/setup.cfg +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/setup.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/altcodepro_polydb_python.egg-info/dependency_links.txt +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/altcodepro_polydb_python.egg-info/requires.txt +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/altcodepro_polydb_python.egg-info/top_level.txt +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/PolyDB.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/AzureBlobStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/AzureFileStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/AzureQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/BlockchainBlobAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/BlockchainFileAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/BlockchainKVAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/BlockchainQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/DynamoDBAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/EFSAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/FirestoreAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/GCPFilestoreAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/GCPPubSubAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/GCPStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/MongoDBAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/PostgreSQLAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/S3Adapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/S3CompatibleAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/SQSAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/VercelBlobAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/VercelFileAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/VercelKVAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/VercelQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/advanced_query.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/audit/AuditStorage.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/audit/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/audit/context.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/audit/manager.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/audit/models.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/base/NoSQLKVAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/base/ObjectStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/base/QueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/base/SharedFilesAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/base/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/batch.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/cache.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/cloudDatabaseFactory.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/databaseFactory.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/decorators.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/errors.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/json_safe.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/monitoring.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/multitenancy.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/observability/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/observability/logging.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/py.typed +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/query.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/registry.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/retry.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/schema.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/security.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/services/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/services/compliance_service.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/services/security_service.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/types.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/validation.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_atomic_decrement.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_aws.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_azure.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_blockchain.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_cloud_factory.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_gcp.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_mongodb.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_multi_engine.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_observability.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_postgresql.py +0 -0
- {altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/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.3.
|
|
3
|
+
Version: 2.3.26
|
|
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.3.
|
|
7
|
+
version = "2.3.26"
|
|
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.3.
|
|
3
|
+
Version: 2.3.26
|
|
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
|
|
@@ -8,7 +8,7 @@ __version__ = "2.2.3"
|
|
|
8
8
|
|
|
9
9
|
from .cloudDatabaseFactory import CloudDatabaseFactory
|
|
10
10
|
from .databaseFactory import DatabaseFactory
|
|
11
|
-
from .models import CloudProvider, PartitionConfig
|
|
11
|
+
from .models import CloudProvider, PartitionConfig, MongoConfig, CosmosMongoConfig
|
|
12
12
|
from .query import QueryBuilder, Operator
|
|
13
13
|
from .audit.context import AuditContext
|
|
14
14
|
from .cache import RedisCacheEngine as CacheEngine
|
|
@@ -36,6 +36,8 @@ __all__ = [
|
|
|
36
36
|
# Models & Config
|
|
37
37
|
"CloudProvider",
|
|
38
38
|
"PartitionConfig",
|
|
39
|
+
"MongoConfig",
|
|
40
|
+
"CosmosMongoConfig",
|
|
39
41
|
# Query
|
|
40
42
|
"QueryBuilder",
|
|
41
43
|
"Operator",
|
|
@@ -567,7 +567,18 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
567
567
|
sval = str(ev).replace("'", "''")
|
|
568
568
|
parts.append(f"{sk} eq '{sval}'")
|
|
569
569
|
|
|
570
|
-
|
|
570
|
+
# Empty-filter contract: if the caller passed filter keys but every
|
|
571
|
+
# condition dropped out (e.g. all values None/unresolvable), the query
|
|
572
|
+
# must match NOTHING — return [] (→ None for a single-object read).
|
|
573
|
+
# Returning all entities here (query_filter=None) is how an unmatched
|
|
574
|
+
# read_one accidentally returned a stray context row. Only a genuinely
|
|
575
|
+
# empty filter dict means "list all".
|
|
576
|
+
if not parts:
|
|
577
|
+
if eff_filters:
|
|
578
|
+
return []
|
|
579
|
+
query_filter = None
|
|
580
|
+
else:
|
|
581
|
+
query_filter = " and ".join(parts)
|
|
571
582
|
|
|
572
583
|
entities = table_client.query_entities(query_filter=query_filter)
|
|
573
584
|
|
|
@@ -670,6 +681,10 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
670
681
|
else:
|
|
671
682
|
sval = str(ev).replace("'", "''")
|
|
672
683
|
parts.append(f"{sk} eq '{sval}'")
|
|
684
|
+
# Same empty-filter contract as _query_raw: filter keys that all resolved
|
|
685
|
+
# to no condition must match NOTHING, not list the whole table.
|
|
686
|
+
if not parts and eff_filters:
|
|
687
|
+
return PageResult(items=[], next_cursor=None, has_more=False)
|
|
673
688
|
query_filter = " and ".join(parts) if parts else None
|
|
674
689
|
|
|
675
690
|
azure_ct = None
|
|
@@ -323,3 +323,38 @@ class MongoConfig(StorageConfig):
|
|
|
323
323
|
super().__init__(CloudProvider.MONGODB, name)
|
|
324
324
|
self.mongo_uri = mongo_uri
|
|
325
325
|
self.db_name = db_name
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class CosmosMongoConfig(MongoConfig):
|
|
329
|
+
"""Azure Cosmos DB via the **MongoDB API**.
|
|
330
|
+
|
|
331
|
+
Cosmos-for-Mongo speaks the MongoDB wire protocol, so it needs no adapter of
|
|
332
|
+
its own — it rides the existing ``MongoDBAdapter`` (and thus the whole polydb
|
|
333
|
+
stack) unchanged. This gives the best of both: Azure-native, serverless /
|
|
334
|
+
autoscale, managed billing + monitoring **and** portability — the same app
|
|
335
|
+
runs on MongoDB Atlas by swapping the connection string.
|
|
336
|
+
|
|
337
|
+
Pass a full ``connection_string`` (recommended — works for both RU and vCore),
|
|
338
|
+
or ``account`` + ``key`` to build the standard Cosmos-for-Mongo (RU) URI.
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
def __init__(
|
|
342
|
+
self,
|
|
343
|
+
name: str = "cosmos",
|
|
344
|
+
*,
|
|
345
|
+
connection_string: str = "",
|
|
346
|
+
account: str = "",
|
|
347
|
+
key: str = "",
|
|
348
|
+
db_name: str = "",
|
|
349
|
+
port: int = 10255,
|
|
350
|
+
):
|
|
351
|
+
uri = connection_string
|
|
352
|
+
if not uri and account and key:
|
|
353
|
+
uri = (
|
|
354
|
+
f"mongodb://{account}:{key}@{account}.mongo.cosmos.azure.com:{port}/"
|
|
355
|
+
f"?ssl=true&replicaSet=globaldb&retrywrites=false"
|
|
356
|
+
f"&maxIdleTimeMS=120000&appName=@{account}@"
|
|
357
|
+
)
|
|
358
|
+
super().__init__(name=name, mongo_uri=uri, db_name=db_name)
|
|
359
|
+
self.is_cosmos = True
|
|
360
|
+
self.account = account
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# src/polydb/utils.py
|
|
2
|
+
"""
|
|
3
|
+
Utility functions for validation and logging
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Dict, Any
|
|
10
|
+
from .errors import ValidationError
|
|
11
|
+
|
|
12
|
+
# Loggers already configured by setup_logger(), so per-request adapter
|
|
13
|
+
# construction doesn't reconfigure (and re-clobber) them repeatedly.
|
|
14
|
+
_configured_loggers: set[str] = set()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def validate_table_name(table: str) -> str:
|
|
18
|
+
"""
|
|
19
|
+
Validate table name to prevent SQL injection
|
|
20
|
+
Only allows alphanumeric, underscore, and hyphen
|
|
21
|
+
"""
|
|
22
|
+
if not re.match(r'^[a-zA-Z0-9_-]+$', table):
|
|
23
|
+
raise ValidationError(
|
|
24
|
+
f"Invalid table name: '{table}'. Only alphanumeric, underscore, and hyphen allowed."
|
|
25
|
+
)
|
|
26
|
+
return table
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def validate_column_name(column: str) -> str:
|
|
30
|
+
"""
|
|
31
|
+
Validate column name to prevent SQL injection
|
|
32
|
+
Only allows alphanumeric and underscore
|
|
33
|
+
"""
|
|
34
|
+
if not re.match(r'^[a-zA-Z0-9_]+$', column):
|
|
35
|
+
raise ValidationError(
|
|
36
|
+
f"Invalid column name: '{column}'. Only alphanumeric and underscore allowed."
|
|
37
|
+
)
|
|
38
|
+
return column
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def validate_columns(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
42
|
+
"""
|
|
43
|
+
Validate all column names in data dictionary
|
|
44
|
+
"""
|
|
45
|
+
for key in data.keys():
|
|
46
|
+
validate_column_name(key)
|
|
47
|
+
return data
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def setup_logger(name: str, level: int = logging.INFO) -> logging.Logger:
|
|
51
|
+
"""Return a logger, configuring it at most once.
|
|
52
|
+
|
|
53
|
+
Adapters are constructed per request, so running the full setup on every
|
|
54
|
+
call would (a) reset a level the embedding app deliberately set and (b)
|
|
55
|
+
re-attach a duplicate plain-text handler — which is why "Initialized Azure
|
|
56
|
+
Queue Storage client" spammed on every request in both plain and JSON form.
|
|
57
|
+
|
|
58
|
+
Behaviour:
|
|
59
|
+
* Idempotent — a given logger name is configured only once.
|
|
60
|
+
* Host-managed root — when the embedding app owns root logging (it sets the
|
|
61
|
+
``ALTCODEPRO_ROOT_LOGGING_MANAGED`` sentinel), polydb attaches NO handler
|
|
62
|
+
of its own and does NOT force a level. Records propagate to the host's
|
|
63
|
+
formatter and honour whatever level the host pinned (so the host can
|
|
64
|
+
quiet noisy adapters). This is the standard "library shouldn't seize
|
|
65
|
+
logging" contract.
|
|
66
|
+
* Standalone — with no host managing root, keep the original behaviour:
|
|
67
|
+
set the level and install a plain StreamHandler.
|
|
68
|
+
"""
|
|
69
|
+
logger = logging.getLogger(name)
|
|
70
|
+
if name in _configured_loggers:
|
|
71
|
+
return logger
|
|
72
|
+
_configured_loggers.add(name)
|
|
73
|
+
|
|
74
|
+
host_managed = bool(os.getenv("ALTCODEPRO_ROOT_LOGGING_MANAGED"))
|
|
75
|
+
if host_managed:
|
|
76
|
+
# Let the host own formatting and level; just propagate.
|
|
77
|
+
return logger
|
|
78
|
+
|
|
79
|
+
# Respect a level the caller already pinned explicitly on this logger.
|
|
80
|
+
if logger.level == logging.NOTSET:
|
|
81
|
+
logger.setLevel(level)
|
|
82
|
+
|
|
83
|
+
# Clear existing handlers to avoid duplication in multiprocess scenarios
|
|
84
|
+
if logger.handlers:
|
|
85
|
+
logger.handlers.clear()
|
|
86
|
+
|
|
87
|
+
handler = logging.StreamHandler()
|
|
88
|
+
formatter = logging.Formatter(
|
|
89
|
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
90
|
+
)
|
|
91
|
+
handler.setFormatter(formatter)
|
|
92
|
+
logger.addHandler(handler)
|
|
93
|
+
|
|
94
|
+
return logger
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Unit tests for the Azure Table empty-filter contract (no Azurite needed).
|
|
2
|
+
|
|
3
|
+
Contract: a filter that carried keys but resolved to no conditions (e.g. every
|
|
4
|
+
value was None/unresolvable) must match NOTHING — [] for a query, None for
|
|
5
|
+
read_one — never a full-table scan that returns a stray context row. Only a
|
|
6
|
+
genuinely empty filter dict ({}) means "list all".
|
|
7
|
+
|
|
8
|
+
This is the bug where read_one("Plan", {tier: None}) returned an unrelated User
|
|
9
|
+
row because the empty query_filter scanned the whole table.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
pytest.importorskip("azure.data.tables") # adapter needs the SDK to import
|
|
15
|
+
|
|
16
|
+
from polydb.adapters.AzureTableStorageAdapter import AzureTableStorageAdapter # noqa: E402
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _Model:
|
|
20
|
+
__name__ = "Plan"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _FakeTableClient:
|
|
24
|
+
def __init__(self, entities):
|
|
25
|
+
self.entities = entities
|
|
26
|
+
self.calls = []
|
|
27
|
+
|
|
28
|
+
def query_entities(self, query_filter=None, **kw):
|
|
29
|
+
self.calls.append(query_filter)
|
|
30
|
+
# Emulate Azure Table: a None filter returns EVERY entity.
|
|
31
|
+
return list(self.entities) if query_filter is None else []
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_STRAY = [{"PartitionKey": "t1", "RowKey": "user@x.com",
|
|
35
|
+
"__polydb_model__": "User", "email": "user@x.com"}]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _adapter(fake):
|
|
39
|
+
adp = object.__new__(AzureTableStorageAdapter) # bypass live-connection __init__
|
|
40
|
+
adp._get_table_client = lambda model: fake
|
|
41
|
+
return adp
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_none_valued_filter_matches_nothing():
|
|
45
|
+
fake = _FakeTableClient(_STRAY)
|
|
46
|
+
res = _adapter(fake)._query_raw(_Model, {"tier": None}, 1)
|
|
47
|
+
assert res == [] # no stray record
|
|
48
|
+
assert fake.calls == [] # and no full-table scan was issued
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_empty_filter_lists_all():
|
|
52
|
+
fake = _FakeTableClient(_STRAY)
|
|
53
|
+
res = _adapter(fake)._query_raw(_Model, {}, 10)
|
|
54
|
+
assert fake.calls == [None] # genuine list-all
|
|
55
|
+
assert len(res) == 1
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_real_filter_queries_normally():
|
|
59
|
+
fake = _FakeTableClient(_STRAY)
|
|
60
|
+
_adapter(fake)._query_raw(_Model, {"tier": "free"}, 1)
|
|
61
|
+
assert fake.calls and "tier eq 'free'" in (fake.calls[0] or "")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_query_paged_none_valued_filter_matches_nothing():
|
|
65
|
+
from polydb.models import PageRequest
|
|
66
|
+
|
|
67
|
+
fake = _FakeTableClient(_STRAY)
|
|
68
|
+
page = _adapter(fake).query_paged(_Model, PageRequest(filters={"tier": None}, limit=10))
|
|
69
|
+
assert page.items == [] and page.has_more is False
|
|
70
|
+
assert fake.calls == [] # short-circuited before any scan
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# src/polydb/utils.py
|
|
2
|
-
"""
|
|
3
|
-
Utility functions for validation and logging
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import re
|
|
7
|
-
import logging
|
|
8
|
-
from typing import Dict, Any
|
|
9
|
-
from .errors import ValidationError
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def validate_table_name(table: str) -> str:
|
|
13
|
-
"""
|
|
14
|
-
Validate table name to prevent SQL injection
|
|
15
|
-
Only allows alphanumeric, underscore, and hyphen
|
|
16
|
-
"""
|
|
17
|
-
if not re.match(r'^[a-zA-Z0-9_-]+$', table):
|
|
18
|
-
raise ValidationError(
|
|
19
|
-
f"Invalid table name: '{table}'. Only alphanumeric, underscore, and hyphen allowed."
|
|
20
|
-
)
|
|
21
|
-
return table
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def validate_column_name(column: str) -> str:
|
|
25
|
-
"""
|
|
26
|
-
Validate column name to prevent SQL injection
|
|
27
|
-
Only allows alphanumeric and underscore
|
|
28
|
-
"""
|
|
29
|
-
if not re.match(r'^[a-zA-Z0-9_]+$', column):
|
|
30
|
-
raise ValidationError(
|
|
31
|
-
f"Invalid column name: '{column}'. Only alphanumeric and underscore allowed."
|
|
32
|
-
)
|
|
33
|
-
return column
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def validate_columns(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
37
|
-
"""
|
|
38
|
-
Validate all column names in data dictionary
|
|
39
|
-
"""
|
|
40
|
-
for key in data.keys():
|
|
41
|
-
validate_column_name(key)
|
|
42
|
-
return data
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def setup_logger(name: str, level: int = logging.INFO) -> logging.Logger:
|
|
46
|
-
"""Setup logger with consistent format"""
|
|
47
|
-
logger = logging.getLogger(name)
|
|
48
|
-
logger.setLevel(level)
|
|
49
|
-
|
|
50
|
-
# Clear existing handlers to avoid duplication in multiprocess scenarios
|
|
51
|
-
if logger.hasHandlers():
|
|
52
|
-
logger.handlers.clear()
|
|
53
|
-
|
|
54
|
-
handler = logging.StreamHandler()
|
|
55
|
-
formatter = logging.Formatter(
|
|
56
|
-
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
57
|
-
)
|
|
58
|
-
handler.setFormatter(formatter)
|
|
59
|
-
logger.addHandler(handler)
|
|
60
|
-
|
|
61
|
-
return logger
|
|
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.3.24 → altcodepro_polydb_python-2.3.26}/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
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/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
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/adapters/__init__.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/advanced_query.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/audit/AuditStorage.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/audit/__init__.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/audit/context.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/audit/manager.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/audit/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/base/QueueAdapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/base/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/databaseFactory.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/decorators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/monitoring.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/multitenancy.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.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/services/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/src/polydb/validation.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_atomic_decrement.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_blockchain.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_cloud_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_multi_engine.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_observability.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.24 → altcodepro_polydb_python-2.3.26}/tests/test_postgresql.py
RENAMED
|
File without changes
|
|
File without changes
|