velocity-python 0.0.109__py3-none-any.whl → 0.0.155__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.
- velocity/__init__.py +3 -1
- velocity/app/orders.py +3 -4
- velocity/app/tests/__init__.py +1 -0
- velocity/app/tests/test_email_processing.py +112 -0
- velocity/app/tests/test_payment_profile_sorting.py +191 -0
- velocity/app/tests/test_spreadsheet_functions.py +124 -0
- velocity/aws/__init__.py +3 -0
- velocity/aws/amplify.py +10 -6
- velocity/aws/handlers/__init__.py +2 -0
- velocity/aws/handlers/base_handler.py +248 -0
- velocity/aws/handlers/context.py +167 -2
- velocity/aws/handlers/exceptions.py +16 -0
- velocity/aws/handlers/lambda_handler.py +24 -85
- velocity/aws/handlers/mixins/__init__.py +16 -0
- velocity/aws/handlers/mixins/activity_tracker.py +181 -0
- velocity/aws/handlers/mixins/aws_session_mixin.py +192 -0
- velocity/aws/handlers/mixins/error_handler.py +192 -0
- velocity/aws/handlers/mixins/legacy_mixin.py +53 -0
- velocity/aws/handlers/mixins/standard_mixin.py +73 -0
- velocity/aws/handlers/response.py +1 -1
- velocity/aws/handlers/sqs_handler.py +28 -143
- velocity/aws/tests/__init__.py +1 -0
- velocity/aws/tests/test_lambda_handler_json_serialization.py +120 -0
- velocity/aws/tests/test_response.py +163 -0
- velocity/db/__init__.py +16 -4
- velocity/db/core/decorators.py +20 -4
- velocity/db/core/engine.py +185 -839
- velocity/db/core/result.py +30 -24
- velocity/db/core/row.py +15 -3
- velocity/db/core/table.py +279 -40
- velocity/db/core/transaction.py +19 -11
- velocity/db/exceptions.py +42 -18
- velocity/db/servers/base/__init__.py +9 -0
- velocity/db/servers/base/initializer.py +70 -0
- velocity/db/servers/base/operators.py +98 -0
- velocity/db/servers/base/sql.py +503 -0
- velocity/db/servers/base/types.py +135 -0
- velocity/db/servers/mysql/__init__.py +73 -0
- velocity/db/servers/mysql/operators.py +54 -0
- velocity/db/servers/{mysql_reserved.py → mysql/reserved.py} +2 -14
- velocity/db/servers/mysql/sql.py +718 -0
- velocity/db/servers/mysql/types.py +107 -0
- velocity/db/servers/postgres/__init__.py +59 -11
- velocity/db/servers/postgres/operators.py +34 -0
- velocity/db/servers/postgres/sql.py +474 -120
- velocity/db/servers/postgres/types.py +88 -2
- velocity/db/servers/sqlite/__init__.py +61 -0
- velocity/db/servers/sqlite/operators.py +52 -0
- velocity/db/servers/sqlite/reserved.py +20 -0
- velocity/db/servers/sqlite/sql.py +677 -0
- velocity/db/servers/sqlite/types.py +92 -0
- velocity/db/servers/sqlserver/__init__.py +73 -0
- velocity/db/servers/sqlserver/operators.py +47 -0
- velocity/db/servers/sqlserver/reserved.py +32 -0
- velocity/db/servers/sqlserver/sql.py +805 -0
- velocity/db/servers/sqlserver/types.py +114 -0
- velocity/db/servers/tablehelper.py +117 -91
- velocity/db/tests/__init__.py +1 -0
- velocity/db/tests/common_db_test.py +0 -0
- velocity/db/tests/postgres/__init__.py +1 -0
- velocity/db/tests/postgres/common.py +49 -0
- velocity/db/tests/postgres/test_column.py +29 -0
- velocity/db/tests/postgres/test_connections.py +25 -0
- velocity/db/tests/postgres/test_database.py +21 -0
- velocity/db/tests/postgres/test_engine.py +205 -0
- velocity/db/tests/postgres/test_general_usage.py +88 -0
- velocity/db/tests/postgres/test_imports.py +8 -0
- velocity/db/tests/postgres/test_result.py +19 -0
- velocity/db/tests/postgres/test_row.py +137 -0
- velocity/db/tests/postgres/test_row_comprehensive.py +720 -0
- velocity/db/tests/postgres/test_schema_locking.py +335 -0
- velocity/db/tests/postgres/test_schema_locking_unit.py +115 -0
- velocity/db/tests/postgres/test_sequence.py +34 -0
- velocity/db/tests/postgres/test_sql_comprehensive.py +462 -0
- velocity/db/tests/postgres/test_table.py +101 -0
- velocity/db/tests/postgres/test_table_comprehensive.py +646 -0
- velocity/db/tests/postgres/test_transaction.py +106 -0
- velocity/db/tests/sql/__init__.py +1 -0
- velocity/db/tests/sql/common.py +177 -0
- velocity/db/tests/sql/test_postgres_select_advanced.py +285 -0
- velocity/db/tests/sql/test_postgres_select_variances.py +517 -0
- velocity/db/tests/test_cursor_rowcount_fix.py +150 -0
- velocity/db/tests/test_db_utils.py +221 -0
- velocity/db/tests/test_postgres.py +448 -0
- velocity/db/tests/test_postgres_unchanged.py +81 -0
- velocity/db/tests/test_process_error_robustness.py +292 -0
- velocity/db/tests/test_result_caching.py +279 -0
- velocity/db/tests/test_result_sql_aware.py +117 -0
- velocity/db/tests/test_row_get_missing_column.py +72 -0
- velocity/db/tests/test_schema_locking_initializers.py +226 -0
- velocity/db/tests/test_schema_locking_simple.py +97 -0
- velocity/db/tests/test_sql_builder.py +165 -0
- velocity/db/tests/test_tablehelper.py +486 -0
- velocity/db/utils.py +62 -47
- velocity/misc/conv/__init__.py +2 -0
- velocity/misc/conv/iconv.py +5 -4
- velocity/misc/export.py +1 -4
- velocity/misc/merge.py +1 -1
- velocity/misc/tests/__init__.py +1 -0
- velocity/misc/tests/test_db.py +90 -0
- velocity/misc/tests/test_fix.py +78 -0
- velocity/misc/tests/test_format.py +64 -0
- velocity/misc/tests/test_iconv.py +203 -0
- velocity/misc/tests/test_merge.py +82 -0
- velocity/misc/tests/test_oconv.py +144 -0
- velocity/misc/tests/test_original_error.py +52 -0
- velocity/misc/tests/test_timer.py +74 -0
- velocity/misc/tools.py +0 -1
- {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/METADATA +2 -2
- velocity_python-0.0.155.dist-info/RECORD +129 -0
- velocity/db/core/exceptions.py +0 -70
- velocity/db/servers/mysql.py +0 -641
- velocity/db/servers/sqlite.py +0 -968
- velocity/db/servers/sqlite_reserved.py +0 -208
- velocity/db/servers/sqlserver.py +0 -921
- velocity/db/servers/sqlserver_reserved.py +0 -314
- velocity_python-0.0.109.dist-info/RECORD +0 -56
- {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/WHEEL +0 -0
- {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/licenses/LICENSE +0 -0
- {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: velocity-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.155
|
|
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['
|
|
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:
|