velocity-python 0.0.109__py3-none-any.whl → 0.0.161__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.
Files changed (120) hide show
  1. velocity/__init__.py +3 -1
  2. velocity/app/orders.py +3 -4
  3. velocity/app/tests/__init__.py +1 -0
  4. velocity/app/tests/test_email_processing.py +112 -0
  5. velocity/app/tests/test_payment_profile_sorting.py +191 -0
  6. velocity/app/tests/test_spreadsheet_functions.py +124 -0
  7. velocity/aws/__init__.py +3 -0
  8. velocity/aws/amplify.py +10 -6
  9. velocity/aws/handlers/__init__.py +2 -0
  10. velocity/aws/handlers/base_handler.py +248 -0
  11. velocity/aws/handlers/context.py +251 -2
  12. velocity/aws/handlers/exceptions.py +16 -0
  13. velocity/aws/handlers/lambda_handler.py +24 -85
  14. velocity/aws/handlers/mixins/__init__.py +16 -0
  15. velocity/aws/handlers/mixins/activity_tracker.py +181 -0
  16. velocity/aws/handlers/mixins/aws_session_mixin.py +192 -0
  17. velocity/aws/handlers/mixins/error_handler.py +192 -0
  18. velocity/aws/handlers/mixins/legacy_mixin.py +53 -0
  19. velocity/aws/handlers/mixins/standard_mixin.py +73 -0
  20. velocity/aws/handlers/response.py +1 -1
  21. velocity/aws/handlers/sqs_handler.py +28 -143
  22. velocity/aws/tests/__init__.py +1 -0
  23. velocity/aws/tests/test_lambda_handler_json_serialization.py +120 -0
  24. velocity/aws/tests/test_response.py +163 -0
  25. velocity/db/__init__.py +16 -4
  26. velocity/db/core/decorators.py +48 -13
  27. velocity/db/core/engine.py +187 -840
  28. velocity/db/core/result.py +33 -25
  29. velocity/db/core/row.py +15 -3
  30. velocity/db/core/table.py +493 -50
  31. velocity/db/core/transaction.py +28 -15
  32. velocity/db/exceptions.py +42 -18
  33. velocity/db/servers/base/__init__.py +9 -0
  34. velocity/db/servers/base/initializer.py +70 -0
  35. velocity/db/servers/base/operators.py +98 -0
  36. velocity/db/servers/base/sql.py +503 -0
  37. velocity/db/servers/base/types.py +135 -0
  38. velocity/db/servers/mysql/__init__.py +73 -0
  39. velocity/db/servers/mysql/operators.py +54 -0
  40. velocity/db/servers/{mysql_reserved.py → mysql/reserved.py} +2 -14
  41. velocity/db/servers/mysql/sql.py +718 -0
  42. velocity/db/servers/mysql/types.py +107 -0
  43. velocity/db/servers/postgres/__init__.py +59 -11
  44. velocity/db/servers/postgres/operators.py +34 -0
  45. velocity/db/servers/postgres/sql.py +474 -120
  46. velocity/db/servers/postgres/types.py +88 -2
  47. velocity/db/servers/sqlite/__init__.py +61 -0
  48. velocity/db/servers/sqlite/operators.py +52 -0
  49. velocity/db/servers/sqlite/reserved.py +20 -0
  50. velocity/db/servers/sqlite/sql.py +677 -0
  51. velocity/db/servers/sqlite/types.py +92 -0
  52. velocity/db/servers/sqlserver/__init__.py +73 -0
  53. velocity/db/servers/sqlserver/operators.py +47 -0
  54. velocity/db/servers/sqlserver/reserved.py +32 -0
  55. velocity/db/servers/sqlserver/sql.py +805 -0
  56. velocity/db/servers/sqlserver/types.py +114 -0
  57. velocity/db/servers/tablehelper.py +117 -91
  58. velocity/db/tests/__init__.py +1 -0
  59. velocity/db/tests/common_db_test.py +0 -0
  60. velocity/db/tests/postgres/__init__.py +1 -0
  61. velocity/db/tests/postgres/common.py +49 -0
  62. velocity/db/tests/postgres/test_column.py +29 -0
  63. velocity/db/tests/postgres/test_connections.py +25 -0
  64. velocity/db/tests/postgres/test_database.py +21 -0
  65. velocity/db/tests/postgres/test_engine.py +205 -0
  66. velocity/db/tests/postgres/test_general_usage.py +88 -0
  67. velocity/db/tests/postgres/test_imports.py +8 -0
  68. velocity/db/tests/postgres/test_result.py +19 -0
  69. velocity/db/tests/postgres/test_row.py +137 -0
  70. velocity/db/tests/postgres/test_row_comprehensive.py +720 -0
  71. velocity/db/tests/postgres/test_schema_locking.py +335 -0
  72. velocity/db/tests/postgres/test_schema_locking_unit.py +115 -0
  73. velocity/db/tests/postgres/test_sequence.py +34 -0
  74. velocity/db/tests/postgres/test_sql_comprehensive.py +462 -0
  75. velocity/db/tests/postgres/test_table.py +101 -0
  76. velocity/db/tests/postgres/test_table_comprehensive.py +646 -0
  77. velocity/db/tests/postgres/test_transaction.py +106 -0
  78. velocity/db/tests/sql/__init__.py +1 -0
  79. velocity/db/tests/sql/common.py +177 -0
  80. velocity/db/tests/sql/test_postgres_select_advanced.py +285 -0
  81. velocity/db/tests/sql/test_postgres_select_variances.py +517 -0
  82. velocity/db/tests/test_cursor_rowcount_fix.py +150 -0
  83. velocity/db/tests/test_db_utils.py +270 -0
  84. velocity/db/tests/test_postgres.py +448 -0
  85. velocity/db/tests/test_postgres_unchanged.py +81 -0
  86. velocity/db/tests/test_process_error_robustness.py +292 -0
  87. velocity/db/tests/test_result_caching.py +279 -0
  88. velocity/db/tests/test_result_sql_aware.py +117 -0
  89. velocity/db/tests/test_row_get_missing_column.py +72 -0
  90. velocity/db/tests/test_schema_locking_initializers.py +226 -0
  91. velocity/db/tests/test_schema_locking_simple.py +97 -0
  92. velocity/db/tests/test_sql_builder.py +165 -0
  93. velocity/db/tests/test_tablehelper.py +486 -0
  94. velocity/db/utils.py +129 -51
  95. velocity/misc/conv/__init__.py +2 -0
  96. velocity/misc/conv/iconv.py +5 -4
  97. velocity/misc/export.py +1 -4
  98. velocity/misc/merge.py +1 -1
  99. velocity/misc/tests/__init__.py +1 -0
  100. velocity/misc/tests/test_db.py +90 -0
  101. velocity/misc/tests/test_fix.py +78 -0
  102. velocity/misc/tests/test_format.py +64 -0
  103. velocity/misc/tests/test_iconv.py +203 -0
  104. velocity/misc/tests/test_merge.py +82 -0
  105. velocity/misc/tests/test_oconv.py +144 -0
  106. velocity/misc/tests/test_original_error.py +52 -0
  107. velocity/misc/tests/test_timer.py +74 -0
  108. velocity/misc/tools.py +0 -1
  109. {velocity_python-0.0.109.dist-info → velocity_python-0.0.161.dist-info}/METADATA +2 -2
  110. velocity_python-0.0.161.dist-info/RECORD +129 -0
  111. velocity/db/core/exceptions.py +0 -70
  112. velocity/db/servers/mysql.py +0 -641
  113. velocity/db/servers/sqlite.py +0 -968
  114. velocity/db/servers/sqlite_reserved.py +0 -208
  115. velocity/db/servers/sqlserver.py +0 -921
  116. velocity/db/servers/sqlserver_reserved.py +0 -314
  117. velocity_python-0.0.109.dist-info/RECORD +0 -56
  118. {velocity_python-0.0.109.dist-info → velocity_python-0.0.161.dist-info}/WHEEL +0 -0
  119. {velocity_python-0.0.109.dist-info → velocity_python-0.0.161.dist-info}/licenses/LICENSE +0 -0
  120. {velocity_python-0.0.109.dist-info → velocity_python-0.0.161.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Tests for database utility functions.
4
+ """
5
+
6
+ import unittest
7
+ from src.velocity.db.utils import (
8
+ safe_sort_key_none_last,
9
+ safe_sort_key_none_first,
10
+ safe_sort_key_with_default,
11
+ safe_sort_rows,
12
+ group_by_fields,
13
+ safe_sort_grouped_rows,
14
+ mask_config_for_display,
15
+ mask_sensitive_in_string,
16
+ )
17
+
18
+
19
+ class TestDatabaseUtils(unittest.TestCase):
20
+ """Test database utility functions."""
21
+
22
+ def setUp(self):
23
+ """Set up test data."""
24
+ self.sample_data = [
25
+ {"id": 1, "name": "Alice", "date": "2024-03", "amount": 100},
26
+ {"id": 2, "name": "Bob", "date": None, "amount": 200},
27
+ {"id": 3, "name": "Charlie", "date": "2024-01", "amount": None},
28
+ {"id": 4, "name": "David", "date": "2024-02", "amount": 150},
29
+ {"id": 5, "name": "Eve", "date": None, "amount": 300},
30
+ ]
31
+
32
+ def test_safe_sort_key_none_last(self):
33
+ """Test sorting with None values at the end."""
34
+ sort_key = safe_sort_key_none_last("date")
35
+ sorted_data = sorted(self.sample_data, key=sort_key)
36
+
37
+ dates = [row["date"] for row in sorted_data]
38
+ expected = ["2024-01", "2024-02", "2024-03", None, None]
39
+ self.assertEqual(dates, expected)
40
+
41
+ def test_safe_sort_key_none_first(self):
42
+ """Test sorting with None values at the beginning."""
43
+ sort_key = safe_sort_key_none_first("date")
44
+ sorted_data = sorted(self.sample_data, key=sort_key)
45
+
46
+ dates = [row["date"] for row in sorted_data]
47
+ expected = [None, None, "2024-01", "2024-02", "2024-03"]
48
+ self.assertEqual(dates, expected)
49
+
50
+ def test_safe_sort_key_with_default(self):
51
+ """Test sorting with None values replaced by default."""
52
+ sort_key = safe_sort_key_with_default("date", "1900-01")
53
+ sorted_data = sorted(self.sample_data, key=sort_key)
54
+
55
+ dates = [row["date"] for row in sorted_data]
56
+ expected = [None, None, "2024-01", "2024-02", "2024-03"]
57
+ self.assertEqual(dates, expected)
58
+
59
+ def test_safe_sort_rows_none_last(self):
60
+ """Test safe_sort_rows with none_handling='last'."""
61
+ sorted_data = safe_sort_rows(self.sample_data, "date", none_handling="last")
62
+
63
+ dates = [row["date"] for row in sorted_data]
64
+ expected = ["2024-01", "2024-02", "2024-03", None, None]
65
+ self.assertEqual(dates, expected)
66
+
67
+ def test_safe_sort_rows_none_first(self):
68
+ """Test safe_sort_rows with none_handling='first'."""
69
+ sorted_data = safe_sort_rows(self.sample_data, "date", none_handling="first")
70
+
71
+ dates = [row["date"] for row in sorted_data]
72
+ expected = [None, None, "2024-01", "2024-02", "2024-03"]
73
+ self.assertEqual(dates, expected)
74
+
75
+ def test_safe_sort_rows_with_default(self):
76
+ """Test safe_sort_rows with none_handling='default'."""
77
+ sorted_data = safe_sort_rows(
78
+ self.sample_data, "date", none_handling="default", default_value="1900-01"
79
+ )
80
+
81
+ dates = [row["date"] for row in sorted_data]
82
+ expected = [None, None, "2024-01", "2024-02", "2024-03"]
83
+ self.assertEqual(dates, expected)
84
+
85
+ def test_safe_sort_rows_reverse(self):
86
+ """Test safe_sort_rows with reverse=True."""
87
+ sorted_data = safe_sort_rows(self.sample_data, "date", reverse=True)
88
+
89
+ dates = [row["date"] for row in sorted_data]
90
+ expected = [None, None, "2024-03", "2024-02", "2024-01"]
91
+ self.assertEqual(dates, expected)
92
+
93
+ def test_safe_sort_rows_invalid_none_handling(self):
94
+ """Test safe_sort_rows with invalid none_handling option."""
95
+ with self.assertRaises(ValueError) as context:
96
+ safe_sort_rows(self.sample_data, "date", none_handling="invalid")
97
+
98
+ self.assertIn("Invalid none_handling option", str(context.exception))
99
+
100
+ def test_group_by_fields_single_field(self):
101
+ """Test grouping by a single field."""
102
+ # Add data with same names for grouping
103
+ test_data = [
104
+ {"name": "Alice", "type": "A", "value": 1},
105
+ {"name": "Bob", "type": "B", "value": 2},
106
+ {"name": "Alice", "type": "C", "value": 3},
107
+ {"name": "Bob", "type": "A", "value": 4},
108
+ ]
109
+
110
+ groups = group_by_fields(test_data, "name")
111
+
112
+ self.assertEqual(len(groups), 2)
113
+ self.assertIn(("Alice",), groups)
114
+ self.assertIn(("Bob",), groups)
115
+ self.assertEqual(len(groups[("Alice",)]), 2)
116
+ self.assertEqual(len(groups[("Bob",)]), 2)
117
+
118
+ def test_group_by_fields_multiple_fields(self):
119
+ """Test grouping by multiple fields."""
120
+ test_data = [
121
+ {"name": "Alice", "type": "A", "value": 1},
122
+ {"name": "Bob", "type": "B", "value": 2},
123
+ {"name": "Alice", "type": "A", "value": 3},
124
+ {"name": "Alice", "type": "B", "value": 4},
125
+ ]
126
+
127
+ groups = group_by_fields(test_data, "name", "type")
128
+
129
+ self.assertEqual(len(groups), 3)
130
+ self.assertIn(("Alice", "A"), groups)
131
+ self.assertIn(("Bob", "B"), groups)
132
+ self.assertIn(("Alice", "B"), groups)
133
+ self.assertEqual(len(groups[("Alice", "A")]), 2)
134
+ self.assertEqual(len(groups[("Bob", "B")]), 1)
135
+ self.assertEqual(len(groups[("Alice", "B")]), 1)
136
+
137
+ def test_safe_sort_grouped_rows(self):
138
+ """Test sorting rows within groups."""
139
+ # Create grouped data
140
+ test_data = [
141
+ {"group": "A", "date": "2024-03", "value": 1},
142
+ {"group": "A", "date": None, "value": 2},
143
+ {"group": "A", "date": "2024-01", "value": 3},
144
+ {"group": "B", "date": "2024-02", "value": 4},
145
+ {"group": "B", "date": None, "value": 5},
146
+ ]
147
+
148
+ groups = group_by_fields(test_data, "group")
149
+ sorted_groups = safe_sort_grouped_rows(groups, "date")
150
+
151
+ # Check group A is sorted correctly
152
+ group_a_dates = [row["date"] for row in sorted_groups[("A",)]]
153
+ expected_a = ["2024-01", "2024-03", None]
154
+ self.assertEqual(group_a_dates, expected_a)
155
+
156
+ # Check group B is sorted correctly
157
+ group_b_dates = [row["date"] for row in sorted_groups[("B",)]]
158
+ expected_b = ["2024-02", None]
159
+ self.assertEqual(group_b_dates, expected_b)
160
+
161
+ def test_payment_profile_scenario(self):
162
+ """Test the specific payment profile scenario that was failing."""
163
+ payment_profiles = [
164
+ {
165
+ "sys_id": 1,
166
+ "email_address": "test@example.com",
167
+ "card_number": "1234",
168
+ "expiration_date": "2024-12",
169
+ "status": "active",
170
+ },
171
+ {
172
+ "sys_id": 2,
173
+ "email_address": "test@example.com",
174
+ "card_number": "1234",
175
+ "expiration_date": "2024-06",
176
+ "status": "active",
177
+ },
178
+ {
179
+ "sys_id": 3,
180
+ "email_address": "test@example.com",
181
+ "card_number": "1234",
182
+ "expiration_date": None,
183
+ "status": "active",
184
+ },
185
+ {
186
+ "sys_id": 4,
187
+ "email_address": "other@example.com",
188
+ "card_number": "5678",
189
+ "expiration_date": "2025-01",
190
+ "status": "active",
191
+ },
192
+ {
193
+ "sys_id": 5,
194
+ "email_address": "other@example.com",
195
+ "card_number": "5678",
196
+ "expiration_date": None,
197
+ "status": "active",
198
+ },
199
+ ]
200
+
201
+ # Group by email and card number
202
+ groups = group_by_fields(payment_profiles, "email_address", "card_number")
203
+
204
+ # Sort each group by expiration date
205
+ sorted_groups = safe_sort_grouped_rows(groups, "expiration_date")
206
+
207
+ # Verify we can safely enumerate through each group
208
+ for group_key, group in sorted_groups.items():
209
+ for idx, row in enumerate(group):
210
+ # This should not raise any errors
211
+ self.assertIsInstance(idx, int)
212
+ self.assertIn("sys_id", row)
213
+ self.assertIn("expiration_date", row)
214
+
215
+ # Check specific group sorting
216
+ test_group = sorted_groups[("test@example.com", "1234")]
217
+ exp_dates = [row["expiration_date"] for row in test_group]
218
+ expected = ["2024-06", "2024-12", None]
219
+ self.assertEqual(exp_dates, expected)
220
+
221
+ def test_mask_config_for_display_redacts_direct_passwords(self):
222
+ """Ensure direct password/token fields are masked."""
223
+ config = {
224
+ "host": "db.local",
225
+ "password": "supersecret",
226
+ "token": "abc123",
227
+ }
228
+
229
+ masked = mask_config_for_display(config)
230
+
231
+ self.assertEqual(masked["host"], "db.local")
232
+ self.assertEqual(masked["password"], "*****")
233
+ self.assertEqual(masked["token"], "*****")
234
+
235
+ def test_mask_config_for_display_handles_nested_structures(self):
236
+ """Verify masking applies to nested dicts, lists, tuples, and DSN strings."""
237
+ config = {
238
+ "options": {
239
+ "passwd": "innersecret",
240
+ "hosts": [
241
+ {"url": "postgresql://user:pwd@localhost/db"},
242
+ ("token=xyz",),
243
+ ],
244
+ }
245
+ }
246
+
247
+ masked = mask_config_for_display(config)
248
+
249
+ self.assertEqual(masked["options"]["passwd"], "*****")
250
+ self.assertEqual(
251
+ masked["options"]["hosts"][0]["url"],
252
+ "postgresql://user:*****@localhost/db",
253
+ )
254
+ self.assertEqual(masked["options"]["hosts"][1][0], "token=*****")
255
+
256
+ def test_mask_sensitive_in_string_redacts_key_value_pairs(self):
257
+ """Key/value DSN parameters should be redacted."""
258
+ dsn = "host=db password=abc123;user=test"
259
+ masked = mask_sensitive_in_string(dsn)
260
+ self.assertEqual(masked, "host=db password=*****;user=test")
261
+
262
+ def test_mask_sensitive_in_string_redacts_url_credentials(self):
263
+ """URL style credentials should hide the password portion."""
264
+ url = "postgresql://user:secret@host/db"
265
+ masked = mask_sensitive_in_string(url)
266
+ self.assertEqual(masked, "postgresql://user:*****@host/db")
267
+
268
+
269
+ if __name__ == "__main__":
270
+ unittest.main()