altcodepro-polydb-python 2.5.9__tar.gz → 2.5.10__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.
Files changed (117) hide show
  1. {altcodepro_polydb_python-2.5.9/src/altcodepro_polydb_python.egg-info → altcodepro_polydb_python-2.5.10}/PKG-INFO +1 -1
  2. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/pyproject.toml +1 -1
  3. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10/src/altcodepro_polydb_python.egg-info}/PKG-INFO +1 -1
  4. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/altcodepro_polydb_python.egg-info/SOURCES.txt +1 -0
  5. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/__init__.py +1 -1
  6. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/AzureTableStorageAdapter.py +20 -1
  7. altcodepro_polydb_python-2.5.10/tests/test_azure_table_id_return_value_sanitization_mismatch.py +151 -0
  8. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/LICENSE +0 -0
  9. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/MANIFEST.in +0 -0
  10. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/README.md +0 -0
  11. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/requirements-aws.txt +0 -0
  12. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/requirements-azure.txt +0 -0
  13. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/requirements-dev.txt +0 -0
  14. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/requirements-gcp.txt +0 -0
  15. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/requirements-generic.txt +0 -0
  16. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/requirements.txt +0 -0
  17. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/setup.cfg +0 -0
  18. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/setup.py +0 -0
  19. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/altcodepro_polydb_python.egg-info/dependency_links.txt +0 -0
  20. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/altcodepro_polydb_python.egg-info/requires.txt +0 -0
  21. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/altcodepro_polydb_python.egg-info/top_level.txt +0 -0
  22. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/PolyDB.py +0 -0
  23. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/AWSSecretsManagerAdapter.py +0 -0
  24. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/AzureBlobStorageAdapter.py +0 -0
  25. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/AzureFileStorageAdapter.py +0 -0
  26. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/AzureKeyVaultAdapter.py +0 -0
  27. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/AzureQueueAdapter.py +0 -0
  28. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/BlockchainBlobAdapter.py +0 -0
  29. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/BlockchainFileAdapter.py +0 -0
  30. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/BlockchainKVAdapter.py +0 -0
  31. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/BlockchainQueueAdapter.py +0 -0
  32. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/DynamoDBAdapter.py +0 -0
  33. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/EFSAdapter.py +0 -0
  34. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/FirestoreAdapter.py +0 -0
  35. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/GCPFilestoreAdapter.py +0 -0
  36. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/GCPPubSubAdapter.py +0 -0
  37. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/GCPSecretManagerAdapter.py +0 -0
  38. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/GCPStorageAdapter.py +0 -0
  39. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/KafkaQueueAdapter.py +0 -0
  40. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/MongoDBAdapter.py +0 -0
  41. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/PostgreSQLAdapter.py +0 -0
  42. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/RabbitMQAdapter.py +0 -0
  43. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/S3Adapter.py +0 -0
  44. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/S3CompatibleAdapter.py +0 -0
  45. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/SQSAdapter.py +0 -0
  46. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/VaultAdapter.py +0 -0
  47. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/VercelBlobAdapter.py +0 -0
  48. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/VercelFileAdapter.py +0 -0
  49. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/VercelKVAdapter.py +0 -0
  50. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/VercelQueueAdapter.py +0 -0
  51. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/adapters/__init__.py +0 -0
  52. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/advanced_query.py +0 -0
  53. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/audit/AuditStorage.py +0 -0
  54. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/audit/__init__.py +0 -0
  55. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/audit/context.py +0 -0
  56. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/audit/manager.py +0 -0
  57. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/audit/models.py +0 -0
  58. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/base/NoSQLKVAdapter.py +0 -0
  59. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/base/ObjectStorageAdapter.py +0 -0
  60. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/base/QueueAdapter.py +0 -0
  61. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/base/SecretsAdapter.py +0 -0
  62. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/base/SharedFilesAdapter.py +0 -0
  63. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/base/__init__.py +0 -0
  64. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/batch.py +0 -0
  65. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/cache.py +0 -0
  66. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/cloudDatabaseFactory.py +0 -0
  67. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/databaseFactory.py +0 -0
  68. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/decorators.py +0 -0
  69. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/errors.py +0 -0
  70. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/json_safe.py +0 -0
  71. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/models.py +0 -0
  72. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/monitoring.py +0 -0
  73. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/multitenancy.py +0 -0
  74. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/observability/__init__.py +0 -0
  75. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/observability/logging.py +0 -0
  76. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/py.typed +0 -0
  77. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/query.py +0 -0
  78. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/registry.py +0 -0
  79. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/retry.py +0 -0
  80. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/schema.py +0 -0
  81. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/security.py +0 -0
  82. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/services/__init__.py +0 -0
  83. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/services/compliance_service.py +0 -0
  84. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/services/security_service.py +0 -0
  85. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/types.py +0 -0
  86. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/utils.py +0 -0
  87. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/src/polydb/validation.py +0 -0
  88. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_advanced_query_injection.py +0 -0
  89. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_atomic_decrement.py +0 -0
  90. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_audit_chain_cutover.py +0 -0
  91. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_audit_hmac.py +0 -0
  92. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_aws.py +0 -0
  93. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_azure.py +0 -0
  94. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_azure_queue_receipt_handle.py +0 -0
  95. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_azure_table_empty_filter.py +0 -0
  96. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_azure_table_id_property_persisted.py +0 -0
  97. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_azure_table_unpack_entity.py +0 -0
  98. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_blockchain.py +0 -0
  99. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_cloud_factory.py +0 -0
  100. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_field_encryption.py +0 -0
  101. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_gcp.py +0 -0
  102. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_kafka.py +0 -0
  103. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_like_pattern_escaping.py +0 -0
  104. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_mongodb.py +0 -0
  105. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_multi_engine.py +0 -0
  106. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_multitenancy_ddl_injection.py +0 -0
  107. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_nosql_operator_injection.py +0 -0
  108. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_observability.py +0 -0
  109. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_postgresql.py +0 -0
  110. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_queue_extend_delay_cancel.py +0 -0
  111. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_rabbitmq.py +0 -0
  112. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_redis_kv.py +0 -0
  113. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_schema_ddl_injection.py +0 -0
  114. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_secrets_aws.py +0 -0
  115. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_secrets_vault.py +0 -0
  116. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/tests/test_session_vars.py +0 -0
  117. {altcodepro_polydb_python-2.5.9 → altcodepro_polydb_python-2.5.10}/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.9
3
+ Version: 2.5.10
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.9"
7
+ version = "2.5.10"
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.9
3
+ Version: 2.5.10
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
@@ -91,6 +91,7 @@ tests/test_azure.py
91
91
  tests/test_azure_queue_receipt_handle.py
92
92
  tests/test_azure_table_empty_filter.py
93
93
  tests/test_azure_table_id_property_persisted.py
94
+ tests/test_azure_table_id_return_value_sanitization_mismatch.py
94
95
  tests/test_azure_table_unpack_entity.py
95
96
  tests/test_blockchain.py
96
97
  tests/test_cloud_factory.py
@@ -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.5.9"
7
+ __version__ = "2.5.10"
8
8
 
9
9
  from .cloudDatabaseFactory import CloudDatabaseFactory
10
10
  from .databaseFactory import DatabaseFactory
@@ -566,7 +566,26 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
566
566
  table_client.upsert_entity(reference_entity)
567
567
 
568
568
  restored = self._unpack_entity(model, entity)
569
- restored["id"] = safe_rk
569
+ # Mirror the write-side condition above (line ~521): only
570
+ # synthesize "id" onto the return value when the caller's own
571
+ # data didn't already supply one. Unconditionally overwriting
572
+ # it with safe_rk here was a real, reproduced bug -- whenever
573
+ # the caller's own id needed RowKey sanitization (contains a
574
+ # character Azure forbids in a RowKey: ':', ' ', '#', '?',
575
+ # '/', '\'), the PERSISTED "id" property kept the caller's
576
+ # original, unsanitized value (data["id"], written untouched
577
+ # via reference_entity above) while this return value handed
578
+ # back the sanitized safe_rk instead -- two different strings
579
+ # for the same row. Any caller holding onto that return
580
+ # value's own "id" (e.g. building a follow-up
581
+ # read_one(model, {"id": ...}) call, or an id.map() over a
582
+ # create_many result) would then filter for a value that
583
+ # never matches any real stored "id" property, silently
584
+ # getting nothing back. Reproduced directly before this fix:
585
+ # data={"id": "a:b"} on a model with a rk_field whose value
586
+ # contains ":" persisted "id"="a:b" but returned "id"="a_b".
587
+ if "id" not in data:
588
+ restored["id"] = safe_rk
570
589
 
571
590
  return restored
572
591
 
@@ -0,0 +1,151 @@
1
+ """Unit tests (no Azurite needed) for a real, reproduced bug: `_put_raw`
2
+ unconditionally overwrote its own RETURN value's "id" field with `safe_rk`
3
+ (the Azure-RowKey-sanitized value) even when the caller's own write payload
4
+ already supplied a real "id" -- so whenever that caller-supplied id needed
5
+ RowKey sanitization (contains a character Azure forbids in a RowKey: ':',
6
+ ' ', '#', '?', '/', '\\'), the PERSISTED entity's "id" property (written
7
+ verbatim from the caller's own `data`, untouched) and the RETURNED value's
8
+ "id" (always `safe_rk`) silently disagreed.
9
+
10
+ This is a distinct bug from the one `test_azure_table_id_property_persisted.py`
11
+ already covers and fixed (2.5.9: a create with NO "id" in its own payload
12
+ never landed a real "id" property at all) -- that fix's own condition
13
+ (`if "id" not in data: data = {**data, "id": safe_rk}`) only ever touches
14
+ the WRITE side. The return-value line right below it (`restored["id"] =
15
+ safe_rk`) had no matching condition, so it fired even when `data["id"]`
16
+ was already present and different from `safe_rk`.
17
+
18
+ Real, reproduced downstream symptom (altcodepro-blueprint-engine): the
19
+ `artifacts` model deliberately builds a colon-joined deterministic id
20
+ (`project_id + ':' + definition_id`) with `rk_field` pointing at that same
21
+ value. `core.db.create_many`'s own CEL-built row data already supplies
22
+ this "id" explicitly. The persisted entity's "id" property therefore kept
23
+ the real, colon-joined value -- but every returned row's own "id" (what a
24
+ workflow's `steps.<create_step>.created.map(c, ...c.id...)` reads to
25
+ enqueue a follow-up job) came back with the colon sanitized to an
26
+ underscore instead. The follow-up job's own `core.db.get(id=<that wrong,
27
+ sanitized value>)` then built an OData `id eq '<sanitized>'` filter that
28
+ matched nothing -- every real row existed, but was unreachable by the very
29
+ id its own creation step just handed out.
30
+ """
31
+
32
+ import pytest
33
+
34
+ pytest.importorskip("azure.data.tables") # adapter needs the SDK to import
35
+
36
+ from polydb.adapters.AzureTableStorageAdapter import AzureTableStorageAdapter # noqa: E402
37
+
38
+
39
+ class _Model:
40
+ __name__ = "artifacts"
41
+
42
+
43
+ class _FakeTableClient:
44
+ """The same real filter-evaluating fake
45
+ test_azure_table_id_property_persisted.py already establishes -- reused
46
+ verbatim rather than re-derived, since this bug lives in the identical
47
+ `_query_raw` OData-filter mechanism."""
48
+
49
+ def __init__(self):
50
+ self.store: dict[tuple, dict] = {}
51
+
52
+ def upsert_entity(self, entity, **kw):
53
+ self.store[(entity["PartitionKey"], entity["RowKey"])] = dict(entity)
54
+
55
+ def query_entities(self, query_filter=None, **kw):
56
+ if query_filter is None:
57
+ return list(self.store.values())
58
+ conditions = []
59
+ for part in query_filter.split(" and "):
60
+ prop, _, raw_val = part.partition(" eq ")
61
+ if raw_val.startswith("'") and raw_val.endswith("'"):
62
+ val = raw_val[1:-1].replace("''", "'")
63
+ elif raw_val == "null":
64
+ val = None
65
+ elif raw_val in ("true", "false"):
66
+ val = raw_val == "true"
67
+ else:
68
+ val = float(raw_val) if "." in raw_val else int(raw_val)
69
+ conditions.append((prop, val))
70
+ return [
71
+ e for e in self.store.values() if all(e.get(prop) == val for prop, val in conditions)
72
+ ]
73
+
74
+
75
+ def _adapter(fake):
76
+ adp = object.__new__(AzureTableStorageAdapter) # bypass live-connection __init__
77
+ adp._get_table_client = lambda model: fake
78
+ adp._blob_service = None
79
+ adp.object_storage = None
80
+ return adp
81
+
82
+
83
+ def test_a_caller_supplied_id_needing_rowkey_sanitization_is_returned_unsanitized():
84
+ """The actual reported bug, reproduced with the real shape:
85
+ project_id + ':' + definition_id, used as both "id" and the rk_field
86
+ value -- ':' is not a legal Azure RowKey character."""
87
+ fake = _FakeTableClient()
88
+ adapter = _adapter(fake)
89
+
90
+ caller_id = "485533b9-8d0f-4e5f-92da-670cedfe92f5:backend.backend_schema"
91
+ data = {
92
+ "id": caller_id,
93
+ "artifact_id": caller_id,
94
+ "project_slug": "community-platform-187",
95
+ "tenant_id": "altcodepro",
96
+ "name": "Backend Schema",
97
+ }
98
+ created = adapter._put_raw(_Model, "community-platform-187", caller_id, data)
99
+
100
+ # The load-bearing assertion: the RETURN value's "id" must equal what
101
+ # the caller actually supplied -- never the sanitized RowKey. Before
102
+ # the fix this was "485533b9-...-670cedfe92f5_backend.backend_schema"
103
+ # (colon silently replaced with underscore).
104
+ assert created["id"] == caller_id
105
+
106
+ # And it must agree with what's genuinely persisted -- the real
107
+ # invariant this bug violated (two different strings for the same
108
+ # row's own "id").
109
+ safe_rk = adapter._sanitize_pk_rk(caller_id)
110
+ stored = fake.store[("community-platform-187", safe_rk)]
111
+ assert stored["id"] == caller_id == created["id"]
112
+
113
+ # The actual downstream failure this caused: a follow-up step (e.g.
114
+ # start_artifact_generation's own load_artifact) doing
115
+ # core.db.get(id=<the id create_many just returned>) must find the
116
+ # row. Reproduced BROKEN before this fix -- querying by the correct,
117
+ # returned id found nothing, because the stored property and the
118
+ # returned value disagreed.
119
+ found = adapter._query_raw(_Model, {"id": created["id"], "tenant_id": "altcodepro"}, None)
120
+ assert len(found) == 1
121
+ assert found[0]["name"] == "Backend Schema"
122
+
123
+
124
+ def test_a_caller_supplied_id_needing_no_sanitization_is_unaffected():
125
+ """Negative control: when the caller's own id is already RowKey-safe,
126
+ safe_rk == the caller's id anyway, so this bug was invisible -- exactly
127
+ why it slipped past every existing test until now."""
128
+ fake = _FakeTableClient()
129
+ adapter = _adapter(fake)
130
+
131
+ data = {"id": "caller-chosen-id", "tenant_id": "t1", "name": "x"}
132
+ created = adapter._put_raw(_Model, "t1", "caller-chosen-id", data)
133
+
134
+ assert created["id"] == "caller-chosen-id"
135
+ stored = fake.store[("t1", "caller-chosen-id")]
136
+ assert stored["id"] == "caller-chosen-id"
137
+
138
+
139
+ def test_no_id_in_payload_still_synthesizes_safe_rk_onto_both_sides():
140
+ """The 2.5.9 case must still work identically -- this fix only adds a
141
+ condition around the return-value line, it must not touch the
142
+ no-id-supplied path at all."""
143
+ fake = _FakeTableClient()
144
+ adapter = _adapter(fake)
145
+
146
+ data = {"identity_key": "email:x@example.com", "tenant_id": "altcodepro"}
147
+ created = adapter._put_raw(_Model, "email:x@example.com", "email:x@example.com", data)
148
+
149
+ assert created["id"] == "email_x@example.com"
150
+ stored = fake.store[("email_x@example.com", "email_x@example.com")]
151
+ assert stored["id"] == created["id"] == "email_x@example.com"