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,203 @@
1
+ import unittest
2
+ from decimal import Decimal
3
+ from datetime import date, datetime, time
4
+
5
+ # Import all functions from iconv.py, renaming any that would clash with built-ins.
6
+ from ..conv.iconv import (
7
+ none,
8
+ phone,
9
+ day_of_week,
10
+ date_conv,
11
+ time_conv,
12
+ timestamp,
13
+ email,
14
+ integer,
15
+ boolean,
16
+ rot13,
17
+ pointer,
18
+ money,
19
+ round_to,
20
+ decimal_val,
21
+ ein,
22
+ to_list,
23
+ title,
24
+ lower,
25
+ upper,
26
+ padding,
27
+ string,
28
+ )
29
+
30
+
31
+ class TestConverters(unittest.TestCase):
32
+
33
+ def test_none(self):
34
+ self.assertIsNone(none("None"))
35
+ self.assertIsNone(none("null"))
36
+ self.assertIsNone(none("@null"))
37
+ self.assertIsNone(none(""))
38
+ self.assertIsNone(none(" "))
39
+ self.assertEqual(none("Something"), "Something")
40
+
41
+ def test_phone(self):
42
+ self.assertIsNone(phone(None))
43
+ self.assertIsNone(phone("@null"))
44
+ self.assertIsNone(phone("999999"))
45
+ self.assertEqual(phone("1234567890"), "1234567890")
46
+ self.assertEqual(phone("(123) 456-7890"), "1234567890")
47
+ self.assertEqual(phone("1-234-567-8900"), "2345678900") # leading '1' dropped
48
+ self.assertIsNone(phone("234567890")) # only 9 digits
49
+ self.assertIsNone(phone("223456789012")) # 12 digits not valid
50
+
51
+ def test_day_of_week(self):
52
+ self.assertEqual(day_of_week("monday"), 1)
53
+ self.assertEqual(day_of_week("Mon"), 1)
54
+ self.assertEqual(day_of_week("TUESDAY"), 2)
55
+ self.assertIsNone(day_of_week("xyz"))
56
+ self.assertIsNone(day_of_week(""))
57
+
58
+ def test_date_converter(self):
59
+ self.assertEqual(date_conv("2025-03-07"), date(2025, 3, 7))
60
+ self.assertIsNone(date_conv("2025-99-99"))
61
+ self.assertIsNone(date_conv("None"))
62
+ self.assertIsNone(date_conv("@null"))
63
+ # Test custom format
64
+ self.assertEqual(date_conv("03/07/2025", fmt="%m/%d/%Y"), date(2025, 3, 7))
65
+
66
+ def test_time_converter(self):
67
+ self.assertEqual(time_conv("12:34:56"), time(12, 34, 56))
68
+ self.assertIsNone(time_conv("None"))
69
+ self.assertIsNone(time_conv("25:99:99"))
70
+ # Test custom format
71
+ self.assertEqual(time_conv("01-02-03", fmt="%H-%M-%S"), time(1, 2, 3))
72
+
73
+ def test_timestamp(self):
74
+ self.assertEqual(
75
+ timestamp("2025-03-07 12:34:56"),
76
+ datetime(2025, 3, 7, 12, 34, 56),
77
+ )
78
+ self.assertIsNone(timestamp("2025-99-99 12:34:56"))
79
+ self.assertIsNone(timestamp("None"))
80
+ # Custom format
81
+ self.assertEqual(
82
+ timestamp("03/07/2025 12|34|56", fmt="%m/%d/%Y %H|%M|%S"),
83
+ datetime(2025, 3, 7, 12, 34, 56),
84
+ )
85
+
86
+ def test_email(self):
87
+ self.assertIsNone(email("None"))
88
+ self.assertIsNone(email("not-an-email"))
89
+ self.assertIsNone(email("user@domain")) # no '.' in domain
90
+ self.assertEqual(email("USER@DOMAIN.COM"), "user@domain.com")
91
+
92
+ def test_integer(self):
93
+ self.assertIsNone(integer("None"))
94
+ self.assertIsNone(integer(""))
95
+ self.assertEqual(integer("123"), 123)
96
+ self.assertEqual(integer("+123.45"), 123)
97
+ self.assertEqual(integer("-123.99"), -123)
98
+ self.assertIsNone(integer("123.45.67")) # multiple decimals
99
+
100
+ def test_boolean(self):
101
+ self.assertFalse(boolean("false"))
102
+ self.assertFalse(boolean(""))
103
+ self.assertFalse(boolean("F"))
104
+ self.assertFalse(boolean("0"))
105
+ self.assertFalse(boolean("no"))
106
+ self.assertTrue(boolean("True"))
107
+ self.assertTrue(boolean("any-other-string"))
108
+ self.assertTrue(boolean(True))
109
+ self.assertFalse(boolean(False))
110
+ self.assertFalse(boolean("@null"))
111
+
112
+ def test_rot13(self):
113
+ self.assertIsNone(rot13("None"))
114
+ self.assertEqual(rot13("abc"), "nop")
115
+ self.assertEqual(rot13("NOP"), "ABC")
116
+
117
+ def test_pointer(self):
118
+ self.assertIsNone(pointer("None"))
119
+ self.assertIsNone(pointer("@null"))
120
+ self.assertIsNone(pointer("@new"))
121
+ self.assertIsNone(pointer("abc"))
122
+ self.assertEqual(pointer("123"), 123)
123
+ self.assertEqual(pointer("-123"), -123)
124
+
125
+ def test_money(self):
126
+ self.assertIsNone(money("None"))
127
+ self.assertIsNone(money("abc"))
128
+ self.assertEqual(money("$1,234.56"), Decimal("1234.56"))
129
+ self.assertEqual(money("-$50"), Decimal("-50"))
130
+ self.assertIsNone(money("123.45.67"))
131
+
132
+ def test_round_to(self):
133
+ # When passing data directly
134
+ self.assertEqual(round_to(2, "123.456"), Decimal("123.46"))
135
+ self.assertEqual(round_to(0, "123.56"), Decimal("124"))
136
+ self.assertIsNone(round_to(2, "None"))
137
+ self.assertIsNone(round_to(2, "abc"))
138
+
139
+ # When using as a converter function
140
+ round_2 = round_to(2)
141
+ self.assertEqual(round_2("123.456"), Decimal("123.46"))
142
+ self.assertIsNone(round_2("abc"))
143
+
144
+ def test_decimal_val(self):
145
+ self.assertIsNone(decimal_val("None"))
146
+ self.assertIsNone(decimal_val("abc"))
147
+ self.assertEqual(decimal_val("123.45"), Decimal("123.45"))
148
+ self.assertIsNone(decimal_val("123.45.67"))
149
+
150
+ def test_ein(self):
151
+ self.assertIsNone(ein("None"))
152
+ self.assertIsNone(ein("12345678")) # only 8 digits
153
+ self.assertEqual(ein("12-3456789"), "123456789")
154
+ self.assertEqual(ein("123456789"), "123456789")
155
+
156
+ def test_to_list(self):
157
+ self.assertIsNone(to_list("None"))
158
+ self.assertIsNone(to_list("@null"))
159
+ self.assertIsNone(to_list(""))
160
+ # Already list
161
+ self.assertEqual(to_list(["a", "b"]), ["a", "b"])
162
+ # String that looks like a list
163
+ self.assertEqual(to_list("[1, 2, 3]"), [1, 2, 3])
164
+ # Invalid string that starts/ends with []
165
+ self.assertEqual(to_list("[1, x, 3]"), ["[1, x, 3]"])
166
+ # Single element
167
+ self.assertEqual(to_list("banana"), ["banana"])
168
+
169
+ def test_title(self):
170
+ self.assertEqual(title("hello world"), "Hello World")
171
+ self.assertEqual(title("HELLO WORLD"), "Hello World")
172
+ self.assertEqual(title(""), "")
173
+ self.assertEqual(title("None"), "") # because it becomes None -> ""
174
+
175
+ def test_lower(self):
176
+ self.assertEqual(lower("Hello"), "hello")
177
+ self.assertEqual(lower(""), "")
178
+ self.assertEqual(lower("NONE"), "")
179
+ self.assertEqual(lower("XYZ"), "xyz")
180
+
181
+ def test_upper(self):
182
+ self.assertEqual(upper("Hello"), "HELLO")
183
+ self.assertEqual(upper(""), "")
184
+ self.assertEqual(upper("none"), "")
185
+ self.assertEqual(upper("xyz"), "XYZ")
186
+
187
+ def test_padding(self):
188
+ pad_5 = padding(5, "0")
189
+ self.assertIsNone(pad_5("None"))
190
+ self.assertEqual(pad_5("123"), "00123")
191
+ pad_4_star = padding(4, "*")
192
+ self.assertEqual(pad_4_star("AB"), "**AB")
193
+ self.assertIsNone(pad_4_star("None"))
194
+
195
+ def test_string(self):
196
+ self.assertIsNone(string("None"))
197
+ self.assertIsNone(string("@null"))
198
+ self.assertIsNone(string(""))
199
+ self.assertEqual(string("hello"), "hello")
200
+
201
+
202
+ if __name__ == "__main__":
203
+ unittest.main()
@@ -0,0 +1,82 @@
1
+ import unittest
2
+ from ..merge import deep_merge
3
+
4
+
5
+ class TestDeepMerge(unittest.TestCase):
6
+
7
+ def test_simple_merge(self):
8
+ """Test merging two simple dictionaries with no nested structures."""
9
+ d1 = {"a": 1, "b": 2}
10
+ d2 = {"b": 3, "c": 4}
11
+ result = deep_merge(d1, d2)
12
+ self.assertEqual(result, {"a": 1, "b": 3, "c": 4})
13
+
14
+ def test_nested_merge(self):
15
+ """Test merging two dictionaries with nested dictionaries."""
16
+ d1 = {"a": {"x": 1}, "b": 2}
17
+ d2 = {"a": {"y": 2}, "b": 3}
18
+ result = deep_merge(d1, d2)
19
+ self.assertEqual(result, {"a": {"x": 1, "y": 2}, "b": 3})
20
+
21
+ def test_list_merge(self):
22
+ """Test merging dictionaries with lists, avoiding duplicates."""
23
+ d1 = {"a": [1, 2], "b": 3}
24
+ d2 = {"a": [2, 3], "b": 4}
25
+ result = deep_merge(d1, d2)
26
+ self.assertEqual(result, {"a": [1, 2, 3], "b": 4})
27
+
28
+ def test_deeply_nested_merge(self):
29
+ """Test merging deeply nested dictionaries."""
30
+ d1 = {"a": {"b": {"c": 1}}}
31
+ d2 = {"a": {"b": {"d": 2}}}
32
+ result = deep_merge(d1, d2)
33
+ self.assertEqual(result, {"a": {"b": {"c": 1, "d": 2}}})
34
+
35
+ def test_update_true(self):
36
+ """Test updating the first dictionary in place when update=True."""
37
+ d1 = {"a": 1, "b": {"x": 10}}
38
+ d2 = {"b": {"y": 20}, "c": 3}
39
+ result = deep_merge(d1, d2, update=True)
40
+ self.assertEqual(result, {"a": 1, "b": {"x": 10, "y": 20}, "c": 3})
41
+ self.assertEqual(d1, result) # d1 should be modified in place
42
+
43
+ def test_update_false(self):
44
+ """Test creating a new dictionary when update=False (default)."""
45
+ d1 = {"a": 1, "b": {"x": 10}}
46
+ d2 = {"b": {"y": 20}, "c": 3}
47
+ result = deep_merge(d1, d2)
48
+ self.assertEqual(result, {"a": 1, "b": {"x": 10, "y": 20}, "c": 3})
49
+ self.assertNotEqual(d1, result) # d1 should remain unchanged
50
+
51
+ def test_multiple_dicts(self):
52
+ """Test merging multiple dictionaries."""
53
+ d1 = {"a": 1}
54
+ d2 = {"b": 2}
55
+ d3 = {"c": 3}
56
+ result = deep_merge(d1, d2, d3)
57
+ self.assertEqual(result, {"a": 1, "b": 2, "c": 3})
58
+
59
+ def test_conflicting_types(self):
60
+ """Test conflicting types (list vs. dict), where latter overrides former."""
61
+ d1 = {"a": {"x": 1}}
62
+ d2 = {"a": [1, 2, 3]}
63
+ result = deep_merge(d1, d2)
64
+ self.assertEqual(result, {"a": [1, 2, 3]}) # d2 overrides d1 here
65
+
66
+ def test_empty_dict(self):
67
+ """Test merging with an empty dictionary."""
68
+ d1 = {"a": 1, "b": {"x": 10}}
69
+ d2 = {}
70
+ result = deep_merge(d1, d2)
71
+ self.assertEqual(result, d1) # merging with empty dict should not change d1
72
+
73
+ def test_merge_with_none(self):
74
+ """Test merging where one dictionary has None values."""
75
+ d1 = {"a": 1, "b": None}
76
+ d2 = {"b": 2, "c": None}
77
+ result = deep_merge(d1, d2)
78
+ self.assertEqual(result, {"a": 1, "b": 2, "c": None})
79
+
80
+
81
+ if __name__ == "__main__":
82
+ unittest.main()
@@ -0,0 +1,144 @@
1
+ import unittest
2
+ import datetime
3
+ from ..conv.oconv import (
4
+ none,
5
+ phone,
6
+ day_of_week,
7
+ date_conv,
8
+ time_conv,
9
+ timestamp,
10
+ email,
11
+ pointer,
12
+ rot13,
13
+ boolean,
14
+ money,
15
+ round_to,
16
+ ein,
17
+ to_list,
18
+ title,
19
+ lower,
20
+ upper,
21
+ padding,
22
+ pprint,
23
+ string,
24
+ )
25
+
26
+
27
+ class TestOconvFunctions(unittest.TestCase):
28
+
29
+ def test_none(self):
30
+ self.assertEqual(none("null"), "")
31
+ self.assertEqual(none("None"), "")
32
+ self.assertEqual(none(""), "")
33
+ self.assertEqual(none("valid"), "valid")
34
+
35
+ def test_phone(self):
36
+ self.assertEqual(phone("123-456-7890"), "(123) 456-7890")
37
+ self.assertEqual(phone("(123)4567890"), "(123) 456-7890")
38
+ self.assertEqual(phone("invalid"), "")
39
+ self.assertEqual(phone(None), "")
40
+
41
+ def test_day_of_week(self):
42
+ self.assertEqual(day_of_week(1), "Monday")
43
+ self.assertEqual(day_of_week("2"), "Tuesday")
44
+ self.assertEqual(day_of_week(5, abbrev=True), "Fri")
45
+ self.assertEqual(day_of_week([1, 2, 5], abbrev=True), "Mon,Tue,Fri")
46
+ self.assertEqual(day_of_week("invalid"), "")
47
+
48
+ def test_date_conv(self):
49
+ """Tests the date_conv function with various inputs."""
50
+ self.assertEqual(date_conv(datetime.datetime(2023, 1, 1)), "2023-01-01")
51
+ self.assertEqual(date_conv(datetime.date(2023, 1, 1)), "2023-01-01")
52
+ self.assertEqual(date_conv("not a date"), "not a date")
53
+
54
+ def test_time_conv(self):
55
+ """Tests the time_conv function with various inputs."""
56
+ self.assertEqual(
57
+ time_conv(datetime.datetime(2023, 1, 1, 12, 30, 45)), "12:30:45"
58
+ )
59
+ self.assertEqual(time_conv(datetime.time(12, 30, 45)), "12:30:45")
60
+ self.assertEqual(time_conv("invalid"), "invalid")
61
+
62
+ def test_timestamp(self):
63
+ self.assertEqual(
64
+ timestamp(datetime.datetime(2023, 1, 1, 12, 30, 45)),
65
+ "Sun Jan 1 12:30:45 2023",
66
+ )
67
+ self.assertEqual(timestamp("invalid"), "invalid")
68
+
69
+ def test_email(self):
70
+ self.assertEqual(email("EXAMPLE@domain.com"), "example@domain.com")
71
+ self.assertEqual(email("None"), "")
72
+ self.assertEqual(email(None), "")
73
+
74
+ def test_pointer(self):
75
+ self.assertEqual(pointer("123"), 123)
76
+ self.assertEqual(pointer("invalid"), "")
77
+ self.assertEqual(pointer(None), "")
78
+
79
+ def test_rot13(self):
80
+ self.assertEqual(rot13("hello"), "uryyb")
81
+ self.assertEqual(rot13("uryyb"), "hello")
82
+
83
+ def test_boolean(self):
84
+ self.assertFalse(boolean("false"))
85
+ self.assertTrue(boolean("true"))
86
+ self.assertTrue(boolean(True))
87
+ self.assertFalse(boolean(False))
88
+
89
+ def test_money(self):
90
+ self.assertEqual(money("1234.5"), "$1,234.50")
91
+ self.assertEqual(money("-1234.56"), "-$1,234.56")
92
+ self.assertEqual(money("None"), "")
93
+ self.assertEqual(money(None), "")
94
+
95
+ def test_round_to(self):
96
+ self.assertEqual(round_to(2, "123.456"), "123.46")
97
+ self.assertEqual(round_to(1, "123.456"), "123.5")
98
+ round_func = round_to(1)
99
+ self.assertEqual(round_func("123.45"), "123.5")
100
+
101
+ def test_ein(self):
102
+ self.assertEqual(ein("123456789"), "12-3456789")
103
+ self.assertEqual(ein("12-3456789"), "12-3456789")
104
+ self.assertEqual(ein("invalid"), "")
105
+
106
+ def test_to_list(self):
107
+ self.assertEqual(to_list("[1, 2, 3]"), [1, 2, 3])
108
+ self.assertEqual(to_list("single"), ["single"])
109
+ self.assertEqual(to_list(["already", "a", "list"]), ["already", "a", "list"])
110
+ self.assertIsNone(to_list("None"))
111
+
112
+ def test_title(self):
113
+ self.assertEqual(title("hello world"), "Hello World")
114
+ self.assertEqual(title("None"), "")
115
+ self.assertEqual(title(None), "")
116
+
117
+ def test_lower(self):
118
+ self.assertEqual(lower("HELLO"), "hello")
119
+ self.assertEqual(lower("None"), "")
120
+ self.assertEqual(lower(None), "")
121
+
122
+ def test_upper(self):
123
+ self.assertEqual(upper("hello"), "HELLO")
124
+ self.assertEqual(upper("None"), "")
125
+ self.assertEqual(upper(None), "")
126
+
127
+ def test_padding(self):
128
+ pad_func = padding(10, " ")
129
+ self.assertEqual(pad_func("123"), " 123")
130
+ self.assertEqual(pad_func(None), "")
131
+ self.assertEqual(padding(5, "0")("12"), "00012")
132
+
133
+ def test_pprint(self):
134
+ self.assertEqual(pprint("[1, 2, 3]"), "[1, 2, 3]")
135
+ self.assertEqual(pprint("invalid"), "invalid")
136
+
137
+ def test_string(self):
138
+ self.assertEqual(string("text"), "text")
139
+ self.assertEqual(string(None), "")
140
+ self.assertEqual(string(""), "")
141
+
142
+
143
+ if __name__ == "__main__":
144
+ unittest.main()
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Test script to demonstrate the original error
4
+
5
+
6
+ def test_original_error():
7
+ """Demonstrate the original error that was happening"""
8
+
9
+ # Simulate what duplicate_rows() was returning (individual dicts, not groups)
10
+ # The code was expecting groups but getting individual rows
11
+ fake_groups = [
12
+ {
13
+ "sys_id": 1,
14
+ "email_address": "test1@example.com",
15
+ "card_number": "1234",
16
+ "expiration_date": "2024-01",
17
+ "status": None,
18
+ },
19
+ {
20
+ "sys_id": 2,
21
+ "email_address": "test1@example.com",
22
+ "card_number": "1234",
23
+ "expiration_date": "2024-02",
24
+ "status": None,
25
+ },
26
+ ]
27
+
28
+ print("Testing original problematic code pattern:")
29
+
30
+ for group in fake_groups: # group is actually a single row/dict
31
+ print(f"Processing 'group': {group}")
32
+ try:
33
+ # This is the line that was failing: sorted(group, key=lambda x: x["expiration_date"])
34
+ # When group is a dict, sorted() iterates over the keys (strings), not the values
35
+ sorted_group = sorted(group, key=lambda x: x["expiration_date"])
36
+ print(f" Sorted result: {sorted_group}")
37
+ except TypeError as e:
38
+ print(f" ERROR: {e}")
39
+ print(
40
+ f" This happened because 'group' is a dict, so sorted() iterates over keys: {list(group.keys())}"
41
+ )
42
+ print(
43
+ f" The lambda tries to access x['expiration_date'] where x is a string key, not a dict"
44
+ )
45
+ return False
46
+
47
+ return True
48
+
49
+
50
+ if __name__ == "__main__":
51
+ print("Demonstrating the original error:")
52
+ test_original_error()
@@ -0,0 +1,74 @@
1
+ import unittest
2
+ import time
3
+ from ..timer import Timer
4
+
5
+
6
+ class TestTimer(unittest.TestCase):
7
+ def test_initialization(self):
8
+ """Test that a Timer object is initialized with the correct label and zero elapsed time."""
9
+ timer = Timer("Test Timer")
10
+ self.assertEqual(timer._label, "Test Timer")
11
+ self.assertIsNotNone(timer._start)
12
+ self.assertEqual(timer._diff, None)
13
+
14
+ def test_start(self):
15
+ """Test that calling start() resets the timer's start time and clears any previous elapsed time."""
16
+ timer = Timer("Test Timer")
17
+ time.sleep(0.1)
18
+ timer.stop()
19
+ initial_diff = timer._diff
20
+
21
+ timer.start()
22
+ self.assertEqual(timer._diff, None) # _diff should be reset
23
+ self.assertNotEqual(timer._start, None) # start time should be reset
24
+ self.assertLess(
25
+ timer.elapsed(), initial_diff
26
+ ) # elapsed time should be smaller after restart
27
+
28
+ def test_stop(self):
29
+ """Test that stop() records the correct elapsed time."""
30
+ timer = Timer("Test Timer")
31
+ time.sleep(0.1)
32
+ timer.stop()
33
+ self.assertGreater(timer._diff, 0)
34
+ self.assertAlmostEqual(
35
+ timer._diff, timer.elapsed(), delta=0.01
36
+ ) # check recorded time
37
+
38
+ def test_elapsed_during_run(self):
39
+ """Test that elapsed() shows increasing time while the timer is running."""
40
+ timer = Timer("Test Timer")
41
+ time.sleep(0.1)
42
+ elapsed_1 = timer.elapsed()
43
+ time.sleep(0.1)
44
+ elapsed_2 = timer.elapsed()
45
+ self.assertGreater(elapsed_2, elapsed_1) # time should increase while running
46
+
47
+ def test_str_running_timer(self):
48
+ """Test the __str__ representation while the timer is running."""
49
+ timer = Timer("Running Timer")
50
+ time.sleep(0.1)
51
+ output = str(timer)
52
+ self.assertIn("Running Timer:", output)
53
+ self.assertIn("s", output) # Should include seconds in the output
54
+
55
+ def test_str_stopped_timer(self):
56
+ """Test the __str__ representation after stopping the timer."""
57
+ timer = Timer("Stopped Timer")
58
+ time.sleep(0.1)
59
+ timer.stop()
60
+ output = str(timer)
61
+ self.assertIn("Stopped Timer:", output)
62
+ self.assertIn("s", output) # Should include seconds in the output
63
+ self.assertEqual(float(output.split(": ")[1][:-2]), float(f"{timer._diff:.4f}"))
64
+
65
+ def test_stop_without_start_error(self):
66
+ """Test that stopping a timer without starting it does not raise errors."""
67
+ timer = Timer("Test Timer")
68
+ timer.start() # reset start
69
+ timer.stop()
70
+ self.assertGreater(timer._diff, 0) # timer should stop correctly
71
+
72
+
73
+ if __name__ == "__main__":
74
+ unittest.main()
velocity/misc/tools.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import os
2
- import inspect
3
2
  import hashlib
4
3
 
5
4
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.0.109
3
+ Version: 0.0.161
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <info@codeclubs.org>
6
6
  License-Expression: MIT
@@ -497,7 +497,7 @@ def update_user(tx):
497
497
  # Find and update using dictionary syntax
498
498
  user = users.find(123) # Returns a row that behaves like a dict
499
499
  user['name'] = 'Updated Name' # Direct assignment like a dict
500
- user['updated_at'] = datetime.now() # No special methods needed
500
+ user['important_date'] = datetime.now() # No special methods needed
501
501
 
502
502
  # Check if columns exist before updating
503
503
  if 'phone' in user: