velocity-python 0.0.132__py3-none-any.whl → 0.0.135__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.
Potentially problematic release.
This version of velocity-python might be problematic. Click here for more details.
- velocity/__init__.py +1 -1
- 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/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/core/decorators.py +20 -3
- velocity/db/core/engine.py +33 -7
- velocity/db/exceptions.py +7 -0
- velocity/db/servers/base/initializer.py +2 -1
- velocity/db/servers/mysql/__init__.py +13 -4
- velocity/db/servers/postgres/__init__.py +14 -4
- velocity/db/servers/sqlite/__init__.py +13 -4
- velocity/db/servers/sqlserver/__init__.py +13 -4
- 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 +707 -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 +471 -0
- velocity/db/tests/postgres/test_table.py +101 -0
- velocity/db/tests/postgres/test_table_comprehensive.py +644 -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 +212 -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/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_python-0.0.132.dist-info → velocity_python-0.0.135.dist-info}/METADATA +1 -1
- velocity_python-0.0.135.dist-info/RECORD +128 -0
- velocity_python-0.0.132.dist-info/RECORD +0 -76
- {velocity_python-0.0.132.dist-info → velocity_python-0.0.135.dist-info}/WHEEL +0 -0
- {velocity_python-0.0.132.dist-info → velocity_python-0.0.135.dist-info}/licenses/LICENSE +0 -0
- {velocity_python-0.0.132.dist-info → velocity_python-0.0.135.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import decimal
|
|
3
|
+
from datetime import datetime, date, time, timedelta
|
|
4
|
+
from ..format import gallons, gallons2liters, currency, human_delta, to_json
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestYourModule(unittest.TestCase):
|
|
8
|
+
|
|
9
|
+
def test_gallons(self):
|
|
10
|
+
"""Tests the gallons function with various inputs."""
|
|
11
|
+
self.assertEqual(gallons("10.5"), "10.50")
|
|
12
|
+
self.assertEqual(gallons(10.5), "10.50")
|
|
13
|
+
self.assertEqual(gallons(decimal.Decimal("10.5")), "10.50")
|
|
14
|
+
self.assertEqual(gallons(None), "")
|
|
15
|
+
self.assertEqual(gallons("invalid"), "")
|
|
16
|
+
|
|
17
|
+
def test_gallons2liters(self):
|
|
18
|
+
"""Tests the gallons2liters function with various inputs."""
|
|
19
|
+
self.assertEqual(gallons2liters("10"), "37.85") # 10 gallons to liters
|
|
20
|
+
self.assertEqual(gallons2liters(1), "3.79") # 1 gallon to liters
|
|
21
|
+
self.assertEqual(gallons2liters(decimal.Decimal("1")), "3.79")
|
|
22
|
+
self.assertEqual(gallons2liters(None), "")
|
|
23
|
+
self.assertEqual(gallons2liters("invalid"), "")
|
|
24
|
+
|
|
25
|
+
def test_currency(self):
|
|
26
|
+
"""Tests the currency function with various inputs."""
|
|
27
|
+
self.assertEqual(currency("1000.5"), "1000.50")
|
|
28
|
+
self.assertEqual(currency(1000.5), "1000.50")
|
|
29
|
+
self.assertEqual(currency(decimal.Decimal("1000.5")), "1000.50")
|
|
30
|
+
self.assertEqual(currency(None), "")
|
|
31
|
+
self.assertEqual(currency("invalid"), "")
|
|
32
|
+
|
|
33
|
+
def test_human_delta(self):
|
|
34
|
+
"""Tests the human_delta function with various timedelta values."""
|
|
35
|
+
self.assertEqual(human_delta(timedelta(seconds=45)), "45 sec")
|
|
36
|
+
self.assertEqual(human_delta(timedelta(minutes=2, seconds=15)), "2 min 15 sec")
|
|
37
|
+
self.assertEqual(
|
|
38
|
+
human_delta(timedelta(hours=1, minutes=5)), "1 hr(s) 5 min 0 sec"
|
|
39
|
+
)
|
|
40
|
+
self.assertEqual(
|
|
41
|
+
human_delta(timedelta(days=2, hours=4, minutes=30)),
|
|
42
|
+
"2 day(s) 4 hr(s) 30 min 0 sec",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def test_to_json(self):
|
|
46
|
+
"""Tests the to_json function with various custom objects."""
|
|
47
|
+
obj = {
|
|
48
|
+
"name": "Test",
|
|
49
|
+
"price": decimal.Decimal("19.99"),
|
|
50
|
+
"date": date(2023, 1, 1),
|
|
51
|
+
"datetime": datetime(2023, 1, 1, 12, 30, 45),
|
|
52
|
+
"time": time(12, 30, 45),
|
|
53
|
+
"duration": timedelta(days=1, hours=2, minutes=30),
|
|
54
|
+
}
|
|
55
|
+
result = to_json(obj)
|
|
56
|
+
self.assertIn('"price": 19.99', result)
|
|
57
|
+
self.assertIn('"date": "2023-01-01"', result)
|
|
58
|
+
self.assertIn('"datetime": "2023-01-01 12:30:45"', result)
|
|
59
|
+
self.assertIn('"time": "12:30:45"', result)
|
|
60
|
+
self.assertIn('"duration": "1 day(s) 2 hr(s) 30 min 0 sec"', result)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
unittest.main()
|
|
@@ -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()
|