aws-python-helper 1.9.0__tar.gz → 1.9.1__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 (61) hide show
  1. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/PKG-INFO +1 -1
  2. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/repository/audit.py +22 -0
  3. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper.egg-info/PKG-INFO +1 -1
  4. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper.egg-info/SOURCES.txt +2 -1
  5. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/pyproject.toml +1 -1
  6. aws_python_helper-1.9.1/tests/test_audit_stamp.py +88 -0
  7. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/README.md +0 -0
  8. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/__init__.py +0 -0
  9. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/api/__init__.py +0 -0
  10. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/api/auth_middleware.py +0 -0
  11. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/api/auth_validators.py +0 -0
  12. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/api/base.py +0 -0
  13. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/api/dispatcher.py +0 -0
  14. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/api/exceptions.py +0 -0
  15. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/api/fetcher.py +0 -0
  16. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/api/handler.py +0 -0
  17. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/context/__init__.py +0 -0
  18. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/context/session.py +0 -0
  19. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/context/state_validator.py +0 -0
  20. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/database/__init__.py +0 -0
  21. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/database/database_proxy.py +0 -0
  22. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/database/external_database_proxy.py +0 -0
  23. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/database/external_mongo_manager.py +0 -0
  24. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/database/mongo_manager.py +0 -0
  25. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/fargate/__init__.py +0 -0
  26. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/fargate/executor.py +0 -0
  27. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/fargate/fetcher.py +0 -0
  28. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/fargate/handler.py +0 -0
  29. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/fargate/task_base.py +0 -0
  30. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/invoker/__init__.py +0 -0
  31. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/invoker/api_client.py +0 -0
  32. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/invoker/exceptions.py +0 -0
  33. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/invoker/lambda_invoker.py +0 -0
  34. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/lambda_standalone/__init__.py +0 -0
  35. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/lambda_standalone/base.py +0 -0
  36. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/lambda_standalone/fetcher.py +0 -0
  37. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/lambda_standalone/handler.py +0 -0
  38. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/model_index_sync/__init__.py +0 -0
  39. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/model_index_sync/base.py +0 -0
  40. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/model_query/__init__.py +0 -0
  41. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/model_query/base.py +0 -0
  42. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/monitoring/__init__.py +0 -0
  43. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/monitoring/run.py +0 -0
  44. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/permissions/__init__.py +0 -0
  45. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/permissions/catalog.py +0 -0
  46. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/repository/__init__.py +0 -0
  47. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/repository/base.py +0 -0
  48. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/sns/__init__.py +0 -0
  49. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/sns/publisher.py +0 -0
  50. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/sqs/__init__.py +0 -0
  51. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/sqs/consumer_base.py +0 -0
  52. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/sqs/fetcher.py +0 -0
  53. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/sqs/handler.py +0 -0
  54. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/utils/__init__.py +0 -0
  55. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/utils/json_encoder.py +0 -0
  56. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/utils/response.py +0 -0
  57. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper/utils/serializer.py +0 -0
  58. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper.egg-info/dependency_links.txt +0 -0
  59. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper.egg-info/requires.txt +0 -0
  60. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/aws_python_helper.egg-info/top_level.txt +0 -0
  61. {aws_python_helper-1.9.0 → aws_python_helper-1.9.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-python-helper
3
- Version: 1.9.0
3
+ Version: 1.9.1
4
4
  Summary: AWS Python Helper Framework
5
5
  Author-email: Fabian Claros <neufabiae@gmail.com>
6
6
  License-Expression: MIT
@@ -124,6 +124,23 @@ def _merge_into_set(update: Dict[str, Any], values: Dict[str, Any], operator: st
124
124
  section.setdefault(key, value)
125
125
 
126
126
 
127
+ def _written_elsewhere(update: Dict[str, Any], key: str, operator: str) -> bool:
128
+ """Si otro operador de esta misma actualización ya escribe ese campo.
129
+
130
+ Mongo rechaza el update entero si dos operadores tocan la misma ruta —
131
+ `$set: {status}` junto a `$setOnInsert: {status}` da "would create a
132
+ conflict at 'status'" — y el sello no tiene por qué ser el que lo provoque.
133
+ Si el llamador ya escribe el campo, el insert va a quedar con su valor, así
134
+ que el sello ahí sobra.
135
+ """
136
+ for other, section in update.items():
137
+ if other == operator or not other.startswith('$') or not isinstance(section, dict):
138
+ continue
139
+ if key in section:
140
+ return True
141
+ return False
142
+
143
+
127
144
  def stamp_update(
128
145
  update: Union[Dict[str, Any], List[Dict[str, Any]]],
129
146
  upsert: bool = False,
@@ -164,6 +181,11 @@ def stamp_update(
164
181
  born = {CREATED_AT: now, CREATED_BY: who}
165
182
  if statuses:
166
183
  born[STATUS] = default_status or statuses[0]
184
+ born = {
185
+ key: value
186
+ for key, value in born.items()
187
+ if not _written_elsewhere(update, key, '$setOnInsert')
188
+ }
167
189
  _merge_into_set(update, born, '$setOnInsert')
168
190
  return update
169
191
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-python-helper
3
- Version: 1.9.0
3
+ Version: 1.9.1
4
4
  Summary: AWS Python Helper Framework
5
5
  Author-email: Fabian Claros <neufabiae@gmail.com>
6
6
  License-Expression: MIT
@@ -55,4 +55,5 @@ aws_python_helper/sqs/handler.py
55
55
  aws_python_helper/utils/__init__.py
56
56
  aws_python_helper/utils/json_encoder.py
57
57
  aws_python_helper/utils/response.py
58
- aws_python_helper/utils/serializer.py
58
+ aws_python_helper/utils/serializer.py
59
+ tests/test_audit_stamp.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aws-python-helper"
7
- version = "1.9.0"
7
+ version = "1.9.1"
8
8
  description = "AWS Python Helper Framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -0,0 +1,88 @@
1
+ """What the audit stamp must never do to a write.
2
+
3
+ Runs on its own, no dependencies:
4
+
5
+ python tests/test_audit_stamp.py
6
+
7
+ These are here because the stamp broke a write in production once: an upsert
8
+ that set `status` itself collided with the `$setOnInsert` the stamp added, and
9
+ Mongo refused the whole update with "would create a conflict at 'status'". It
10
+ failed at runtime, on one endpoint, in a log nobody was reading.
11
+ """
12
+
13
+ import os
14
+ import sys
15
+
16
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
17
+
18
+ from aws_python_helper.repository.audit import stamp_update # noqa: E402
19
+
20
+ STATUSES = ["active", "inactive"]
21
+
22
+ failures = []
23
+
24
+
25
+ def check(label, condition, detail=""):
26
+ mark = "ok " if condition else "FAIL"
27
+ print(f" {mark} {label}{' ' + detail if detail else ''}")
28
+ if not condition:
29
+ failures.append(label)
30
+
31
+
32
+ def conflicts(update):
33
+ """Los campos que dos operadores de un mismo update escriben a la vez.
34
+
35
+ Es lo que Mongo rechaza, y rechaza el update entero: no escribe nada.
36
+ """
37
+ sections = [
38
+ (name, section) for name, section in update.items()
39
+ if name.startswith("$") and isinstance(section, dict)
40
+ ]
41
+ clash = set()
42
+ for index, (_, first) in enumerate(sections):
43
+ for _, second in sections[index + 1:]:
44
+ clash |= set(first) & set(second)
45
+ return clash
46
+
47
+
48
+ print("no two operators may write the same path")
49
+ for label, update in [
50
+ ("upsert that sets status itself", {"$set": {"name": "ct", "status": "active"}}),
51
+ ("upsert that sets created_at itself", {"$set": {"created_at": "fixed"}}),
52
+ ("upsert that sets created_by itself", {"$set": {"created_by": {"name": "x"}}}),
53
+ ("plain upsert", {"$set": {"name": "ct"}}),
54
+ ("upsert with $inc alongside", {"$set": {"name": "ct"}, "$inc": {"hits": 1}}),
55
+ ]:
56
+ result = stamp_update(dict(update), upsert=True, statuses=STATUSES, default_status="active")
57
+ found = conflicts(result)
58
+ check(label, not found, f"conflicts={sorted(found)}" if found else "")
59
+
60
+ print("\nthe stamp is still complete when nothing conflicts")
61
+ result = stamp_update({"$set": {"name": "ct"}}, upsert=True, statuses=STATUSES, default_status="active")
62
+ check("status on insert", result["$setOnInsert"].get("status") == "active")
63
+ check("created_at on insert", "created_at" in result["$setOnInsert"])
64
+ check("created_by on insert", "created_by" in result["$setOnInsert"])
65
+ check("updated_at always", "updated_at" in result["$set"])
66
+
67
+ print("\nthe caller's own value wins")
68
+ result = stamp_update({"$set": {"status": "inactive"}}, upsert=True, statuses=STATUSES, default_status="active")
69
+ check("status stays what the caller set", result["$set"]["status"] == "inactive")
70
+ check("no default forced on top", "status" not in result.get("$setOnInsert", {}))
71
+
72
+ print("\nwithout upsert there is no insert stamp")
73
+ result = stamp_update({"$set": {"status": "inactive"}}, upsert=False, statuses=STATUSES)
74
+ check("no $setOnInsert", "$setOnInsert" not in result)
75
+
76
+ print("\nwithout a vocabulary no status is invented")
77
+ result = stamp_update({"$set": {"name": "ct"}}, upsert=True, statuses=None)
78
+ check("no status", "status" not in result.get("$setOnInsert", {}))
79
+
80
+ print("\na pipeline update takes stages, not operators")
81
+ result = stamp_update([{"$set": {"name": "ct"}}], upsert=True, statuses=STATUSES)
82
+ check("still a list", isinstance(result, list))
83
+ check("stamp appended last", result[-1]["$set"].get("updated_at") is not None)
84
+
85
+ if failures:
86
+ print(f"\n{len(failures)} failed: {failures}")
87
+ raise SystemExit(1)
88
+ print("\nall good")