velocity-python 0.0.32__tar.gz → 0.0.34__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of velocity-python might be problematic. Click here for more details.
- {velocity_python-0.0.32 → velocity_python-0.0.34}/PKG-INFO +1 -1
- {velocity_python-0.0.32 → velocity_python-0.0.34}/pyproject.toml +1 -1
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/__init__.py +1 -1
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/decorators.py +2 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/engine.py +3 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/table.py +8 -2
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/transaction.py +13 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/servers/postgres.py +1 -1
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/conv/iconv.py +5 -1
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/conv/oconv.py +54 -24
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity_python.egg-info/SOURCES.txt +2 -0
- velocity_python-0.0.34/tests/test_foreign_key_handling.py +169 -0
- velocity_python-0.0.34/tests/test_iconv.py +207 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_postgres.py +2 -1
- velocity_python-0.0.34/tests/test_postgres_advanced.py +273 -0
- velocity_python-0.0.32/tests/test_iconv.py +0 -141
- {velocity_python-0.0.32 → velocity_python-0.0.34}/LICENSE +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/README.md +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/setup.cfg +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/exceptions.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/servers/mysql.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/servers/sqlite.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/servers/sqlserver.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_db.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_email_processing.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_format.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_merge.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_oconv.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_response.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_timer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: velocity-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.34
|
|
4
4
|
Summary: A rapid application development library for interfacing with data storage
|
|
5
5
|
Author-email: Paul Perez <pperez@codeclubs.org>
|
|
6
6
|
Project-URL: Homepage, https://codeclubs.org/projects/velocity
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from functools import wraps
|
|
2
2
|
from velocity.db import exceptions
|
|
3
|
+
import traceback
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
def retry_on_dup_key(function):
|
|
@@ -55,6 +56,7 @@ def return_default(
|
|
|
55
56
|
if result is None:
|
|
56
57
|
result = default
|
|
57
58
|
except f.exceptions as e:
|
|
59
|
+
traceback.print_exc()
|
|
58
60
|
self.tx.rollback_savepoint(sp, cursor=self.cursor)
|
|
59
61
|
return f.default
|
|
60
62
|
self.tx.release_savepoint(sp, cursor=self.cursor)
|
|
@@ -169,6 +169,8 @@ class Engine(object):
|
|
|
169
169
|
return function(*args, **kwds)
|
|
170
170
|
except exceptions.DbRetryTransaction as e:
|
|
171
171
|
if e.args and e.args[0]:
|
|
172
|
+
print(e)
|
|
173
|
+
print("**Retry Transaction. Rollback and start over")
|
|
172
174
|
_tx.rollback()
|
|
173
175
|
continue
|
|
174
176
|
retry_count += 1
|
|
@@ -249,6 +251,7 @@ class Engine(object):
|
|
|
249
251
|
conf["database"] = database
|
|
250
252
|
if "dbname" in conf:
|
|
251
253
|
conf["dbname"] = database
|
|
254
|
+
|
|
252
255
|
return self
|
|
253
256
|
|
|
254
257
|
def set_config(self, config):
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# table.py
|
|
1
2
|
from velocity.db import exceptions
|
|
2
3
|
from velocity.db.core.row import Row
|
|
3
4
|
from velocity.db.core.result import Result
|
|
@@ -9,8 +10,13 @@ from velocity.db.core.decorators import (
|
|
|
9
10
|
)
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
class Query
|
|
13
|
-
|
|
13
|
+
class Query:
|
|
14
|
+
def __init__(self, sql, params=()):
|
|
15
|
+
self.sql = sql
|
|
16
|
+
self.params = params
|
|
17
|
+
|
|
18
|
+
def __str__(self):
|
|
19
|
+
return self.sql
|
|
14
20
|
|
|
15
21
|
|
|
16
22
|
class Table(object):
|
|
@@ -6,6 +6,7 @@ from velocity.db.core.column import Column
|
|
|
6
6
|
from velocity.db.core.database import Database
|
|
7
7
|
from velocity.db.core.sequence import Sequence
|
|
8
8
|
from velocity.misc.db import randomword
|
|
9
|
+
import traceback
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
debug = False
|
|
@@ -29,6 +30,11 @@ class Transaction(object):
|
|
|
29
30
|
|
|
30
31
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
31
32
|
if exc_type:
|
|
33
|
+
if debug:
|
|
34
|
+
print("Transaction.__exit__")
|
|
35
|
+
tb_str = "".join(traceback.format_exception(exc_type, exc_val, exc_tb))
|
|
36
|
+
if debug:
|
|
37
|
+
print(tb_str)
|
|
32
38
|
self.rollback()
|
|
33
39
|
self.close()
|
|
34
40
|
|
|
@@ -206,3 +212,10 @@ class Transaction(object):
|
|
|
206
212
|
result = self.execute(sql, vals)
|
|
207
213
|
self.__pg_types = dict(result.as_tuple())
|
|
208
214
|
return self.__pg_types
|
|
215
|
+
|
|
216
|
+
def switch_to_database(self, name):
|
|
217
|
+
if self.connection:
|
|
218
|
+
self.connection.close()
|
|
219
|
+
self.connection = None
|
|
220
|
+
self.engine.switch_to_database(name)
|
|
221
|
+
return self
|
|
@@ -83,7 +83,11 @@ def email(data: str) -> Optional[str]:
|
|
|
83
83
|
|
|
84
84
|
def integer(data: str) -> int:
|
|
85
85
|
"""Converts a string to an integer, removing non-numeric characters."""
|
|
86
|
-
|
|
86
|
+
cleaned_data = re.sub(r"[^0-9\.-]", "", data)
|
|
87
|
+
try:
|
|
88
|
+
return int(float(cleaned_data))
|
|
89
|
+
except ValueError:
|
|
90
|
+
raise ValueError(f"Cannot convert {data} to integer.")
|
|
87
91
|
|
|
88
92
|
|
|
89
93
|
def boolean(data: Union[str, bool]) -> bool:
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
# oconv.py
|
|
1
2
|
import re
|
|
2
3
|
import codecs
|
|
3
4
|
import decimal
|
|
4
|
-
|
|
5
|
+
import datetime
|
|
5
6
|
from pprint import pformat
|
|
6
7
|
from typing import Optional, Union, List, Callable
|
|
8
|
+
import ast
|
|
7
9
|
|
|
8
10
|
# Convert SQL data to JS format for display
|
|
9
11
|
|
|
@@ -36,7 +38,15 @@ def day_of_week(data: Union[int, str, List], abbrev: bool = False) -> str:
|
|
|
36
38
|
6: "Saturday",
|
|
37
39
|
7: "Sunday",
|
|
38
40
|
}
|
|
39
|
-
days_abbrev = {
|
|
41
|
+
days_abbrev = {
|
|
42
|
+
1: "Mon",
|
|
43
|
+
2: "Tue",
|
|
44
|
+
3: "Wed",
|
|
45
|
+
4: "Thu",
|
|
46
|
+
5: "Fri",
|
|
47
|
+
6: "Sat",
|
|
48
|
+
7: "Sun",
|
|
49
|
+
}
|
|
40
50
|
days = days_abbrev if abbrev else days_full
|
|
41
51
|
|
|
42
52
|
if isinstance(data, list):
|
|
@@ -48,19 +58,29 @@ def day_of_week(data: Union[int, str, List], abbrev: bool = False) -> str:
|
|
|
48
58
|
return ""
|
|
49
59
|
|
|
50
60
|
|
|
51
|
-
def date(
|
|
61
|
+
def date(
|
|
62
|
+
data: Union[datetime.datetime, datetime.date, str], fmt: str = "%Y-%m-%d"
|
|
63
|
+
) -> str:
|
|
52
64
|
"""Formats a date object as a string according to the specified format."""
|
|
53
|
-
return
|
|
65
|
+
return (
|
|
66
|
+
data.strftime(fmt)
|
|
67
|
+
if isinstance(data, (datetime.datetime, datetime.date))
|
|
68
|
+
else str(data)
|
|
69
|
+
)
|
|
54
70
|
|
|
55
71
|
|
|
56
|
-
def time(data: Union[datetime, time, str], fmt: str = "%X") -> str:
|
|
72
|
+
def time(data: Union[datetime.datetime, datetime.time, str], fmt: str = "%X") -> str:
|
|
57
73
|
"""Formats a time object as a string according to the specified format."""
|
|
58
|
-
return
|
|
74
|
+
return (
|
|
75
|
+
data.strftime(fmt)
|
|
76
|
+
if isinstance(data, (datetime.datetime, datetime.time))
|
|
77
|
+
else str(data)
|
|
78
|
+
)
|
|
59
79
|
|
|
60
80
|
|
|
61
|
-
def timestamp(data: Union[datetime, str], fmt: str = "%c") -> str:
|
|
81
|
+
def timestamp(data: Union[datetime.datetime, str], fmt: str = "%c") -> str:
|
|
62
82
|
"""Formats a datetime object as a string according to the specified format."""
|
|
63
|
-
return data.strftime(fmt) if isinstance(data, datetime) else str(data)
|
|
83
|
+
return data.strftime(fmt) if isinstance(data, datetime.datetime) else str(data)
|
|
64
84
|
|
|
65
85
|
|
|
66
86
|
def email(data: Optional[str]) -> str:
|
|
@@ -88,24 +108,29 @@ def boolean(data: Union[str, bool]) -> bool:
|
|
|
88
108
|
return bool(data)
|
|
89
109
|
|
|
90
110
|
|
|
91
|
-
def money(data: str) -> str:
|
|
92
|
-
"""Formats a numeric
|
|
111
|
+
def money(data: Union[str, float, int, decimal.Decimal]) -> str:
|
|
112
|
+
"""Formats a numeric value as currency."""
|
|
93
113
|
if data in [None, ""]:
|
|
94
114
|
return ""
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
try:
|
|
116
|
+
amount = decimal.Decimal(str(data))
|
|
117
|
+
return f"${amount:,.2f}"
|
|
118
|
+
except (decimal.InvalidOperation, ValueError):
|
|
119
|
+
return ""
|
|
97
120
|
|
|
98
121
|
|
|
99
122
|
def round_to(
|
|
100
123
|
precision: int, data: Optional[Union[str, float, decimal.Decimal]] = None
|
|
101
|
-
) -> Union[Callable, str]:
|
|
124
|
+
) -> Union[Callable[[Union[str, float, decimal.Decimal]], str], str]:
|
|
102
125
|
"""Rounds a number to the specified precision."""
|
|
103
126
|
|
|
104
127
|
def function(value):
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
128
|
+
try:
|
|
129
|
+
amount = decimal.Decimal(str(value))
|
|
130
|
+
rounded = round(amount, precision)
|
|
131
|
+
return f"{rounded:.{precision}f}"
|
|
132
|
+
except (decimal.InvalidOperation, ValueError):
|
|
133
|
+
return "0"
|
|
109
134
|
|
|
110
135
|
return function if data is None else function(data)
|
|
111
136
|
|
|
@@ -125,11 +150,15 @@ def to_list(data: Union[str, List]) -> Optional[List]:
|
|
|
125
150
|
return None
|
|
126
151
|
if isinstance(data, list):
|
|
127
152
|
return data
|
|
128
|
-
if isinstance(data, str)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
153
|
+
if isinstance(data, str):
|
|
154
|
+
data = data.strip()
|
|
155
|
+
if data.startswith("[") and data.endswith("]"):
|
|
156
|
+
try:
|
|
157
|
+
return ast.literal_eval(data)
|
|
158
|
+
except (SyntaxError, ValueError):
|
|
159
|
+
return None
|
|
160
|
+
else:
|
|
161
|
+
return [data]
|
|
133
162
|
return [data]
|
|
134
163
|
|
|
135
164
|
|
|
@@ -160,8 +189,9 @@ def padding(length: int, char: str) -> Callable[[str], str]:
|
|
|
160
189
|
def pprint(data: str) -> str:
|
|
161
190
|
"""Pretty-prints a JSON-like string representation of data."""
|
|
162
191
|
try:
|
|
163
|
-
|
|
164
|
-
|
|
192
|
+
parsed_data = ast.literal_eval(data)
|
|
193
|
+
return pformat(parsed_data)
|
|
194
|
+
except (SyntaxError, ValueError):
|
|
165
195
|
return data
|
|
166
196
|
|
|
167
197
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: velocity-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.34
|
|
4
4
|
Summary: A rapid application development library for interfacing with data storage
|
|
5
5
|
Author-email: Paul Perez <pperez@codeclubs.org>
|
|
6
6
|
Project-URL: Homepage, https://codeclubs.org/projects/velocity
|
|
@@ -42,11 +42,13 @@ src/velocity_python.egg-info/requires.txt
|
|
|
42
42
|
src/velocity_python.egg-info/top_level.txt
|
|
43
43
|
tests/test_db.py
|
|
44
44
|
tests/test_email_processing.py
|
|
45
|
+
tests/test_foreign_key_handling.py
|
|
45
46
|
tests/test_format.py
|
|
46
47
|
tests/test_iconv.py
|
|
47
48
|
tests/test_merge.py
|
|
48
49
|
tests/test_oconv.py
|
|
49
50
|
tests/test_postgres.py
|
|
51
|
+
tests/test_postgres_advanced.py
|
|
50
52
|
tests/test_response.py
|
|
51
53
|
tests/test_spreadsheet_functions.py
|
|
52
54
|
tests/test_timer.py
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from velocity.db.servers import postgres
|
|
3
|
+
|
|
4
|
+
test_db = "test_foreign_key_db"
|
|
5
|
+
engine = postgres.initialize(database=test_db)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@engine.transaction
|
|
9
|
+
class TestForeignKeyHandling(unittest.TestCase):
|
|
10
|
+
@classmethod
|
|
11
|
+
@engine.transaction
|
|
12
|
+
def setUpClass(cls, tx):
|
|
13
|
+
# Drop and recreate the test database
|
|
14
|
+
tx.switch_to_database("postgres")
|
|
15
|
+
tx.execute(f"drop database if exists {test_db}", single=True)
|
|
16
|
+
tx.execute(f"create database {test_db}", single=True)
|
|
17
|
+
tx.switch_to_database(test_db)
|
|
18
|
+
|
|
19
|
+
# Create parent_table
|
|
20
|
+
parent_table = tx.table("parent_table")
|
|
21
|
+
parent_table.create(
|
|
22
|
+
columns={
|
|
23
|
+
"name": str,
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Create middle_table with a foreign key to parent_table
|
|
28
|
+
middle_table = tx.table("middle_table")
|
|
29
|
+
middle_table.create(
|
|
30
|
+
columns={
|
|
31
|
+
"parent_id": int,
|
|
32
|
+
"title": str,
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
middle_table.create_foreign_key("parent_id", "parent_table", "sys_id")
|
|
36
|
+
|
|
37
|
+
# Create child_table with foreign keys to both parent_table and middle_table
|
|
38
|
+
child_table = tx.table("child_table")
|
|
39
|
+
child_table.create(
|
|
40
|
+
columns={
|
|
41
|
+
"parent_id": int,
|
|
42
|
+
"middle_id": int,
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
child_table.create_foreign_key("parent_id", "parent_table", "sys_id")
|
|
46
|
+
child_table.create_foreign_key("middle_id", "middle_table", "sys_id")
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
@engine.transaction
|
|
50
|
+
def tearDownClass(cls, tx):
|
|
51
|
+
# Drop the test database
|
|
52
|
+
# tx.switch_to_database("postgres")
|
|
53
|
+
# tx.execute(f"drop database if exists {test_db}", single=True)
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def test_foreign_key_with_specified_sys_id(self, tx):
|
|
57
|
+
# Insert data into parent_table with specified sys_id
|
|
58
|
+
parent_table = tx.table("parent_table")
|
|
59
|
+
parent_table.insert({"sys_id": 100, "name": "Parent 1"})
|
|
60
|
+
parent_table.insert({"sys_id": 200, "name": "Parent 2"})
|
|
61
|
+
|
|
62
|
+
# Insert data into middle_table with specified sys_id
|
|
63
|
+
middle_table = tx.table("middle_table")
|
|
64
|
+
middle_table.insert({"sys_id": 300, "parent_id": 100, "title": "Title 1"})
|
|
65
|
+
middle_table.insert({"sys_id": 400, "parent_id": 200, "title": "Title 2"})
|
|
66
|
+
|
|
67
|
+
# Insert data into child_table with specified sys_id
|
|
68
|
+
child_table = tx.table("child_table")
|
|
69
|
+
child_table.insert(
|
|
70
|
+
{
|
|
71
|
+
"sys_id": 500,
|
|
72
|
+
"parent_id": 100,
|
|
73
|
+
"middle_id": 300,
|
|
74
|
+
"description": "Child A",
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
child_table.insert(
|
|
78
|
+
{
|
|
79
|
+
"sys_id": 600,
|
|
80
|
+
"parent_id": 200,
|
|
81
|
+
"middle_id": 400,
|
|
82
|
+
"description": "Child B",
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Query selecting columns with three foreign key references
|
|
87
|
+
result = list(
|
|
88
|
+
child_table.select(
|
|
89
|
+
columns=[
|
|
90
|
+
"parent_id>name",
|
|
91
|
+
"middle_id>title",
|
|
92
|
+
"sys_id",
|
|
93
|
+
"description",
|
|
94
|
+
]
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
expected_result = [
|
|
98
|
+
{
|
|
99
|
+
"parent_id_name": "Parent 1",
|
|
100
|
+
"middle_id_title": "Title 1",
|
|
101
|
+
"sys_id": 500,
|
|
102
|
+
"description": "Child A",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"parent_id_name": "Parent 2",
|
|
106
|
+
"middle_id_title": "Title 2",
|
|
107
|
+
"sys_id": 600,
|
|
108
|
+
"description": "Child B",
|
|
109
|
+
},
|
|
110
|
+
]
|
|
111
|
+
self.assertEqual(result, expected_result)
|
|
112
|
+
|
|
113
|
+
def test_foreign_key_conditions_with_specified_sys_id(self, tx):
|
|
114
|
+
# Query with conditions on foreign key references
|
|
115
|
+
child_table = tx.table("child_table")
|
|
116
|
+
result = list(
|
|
117
|
+
child_table.select(
|
|
118
|
+
columns=[
|
|
119
|
+
"parent_id>name",
|
|
120
|
+
"middle_id>title",
|
|
121
|
+
"sys_id",
|
|
122
|
+
"description",
|
|
123
|
+
],
|
|
124
|
+
where={"parent_id>name": "Parent 1", "middle_id>title": "Title 1"},
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
expected_result = [
|
|
128
|
+
{
|
|
129
|
+
"parent_id_name": "Parent 1",
|
|
130
|
+
"middle_id_title": "Title 1",
|
|
131
|
+
"sys_id": 500,
|
|
132
|
+
"description": "Child A",
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
self.assertEqual(result, expected_result)
|
|
136
|
+
|
|
137
|
+
def test_foreign_key_ordering_with_specified_sys_id(self, tx):
|
|
138
|
+
# Query with ordering based on foreign key references
|
|
139
|
+
child_table = tx.table("child_table")
|
|
140
|
+
result = list(
|
|
141
|
+
child_table.select(
|
|
142
|
+
columns=[
|
|
143
|
+
"parent_id>name",
|
|
144
|
+
"middle_id>title",
|
|
145
|
+
"sys_id",
|
|
146
|
+
"description",
|
|
147
|
+
],
|
|
148
|
+
orderby="parent_id>name DESC, middle_id>title ASC",
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
expected_result = [
|
|
152
|
+
{
|
|
153
|
+
"parent_id_name": "Parent 2",
|
|
154
|
+
"middle_id_title": "Title 2",
|
|
155
|
+
"sys_id": 600,
|
|
156
|
+
"description": "Child B",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"parent_id_name": "Parent 1",
|
|
160
|
+
"middle_id_title": "Title 1",
|
|
161
|
+
"sys_id": 500,
|
|
162
|
+
"description": "Child A",
|
|
163
|
+
},
|
|
164
|
+
]
|
|
165
|
+
self.assertEqual(result, expected_result)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if __name__ == "__main__":
|
|
169
|
+
unittest.main()
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# test_iconv.py
|
|
2
|
+
import unittest
|
|
3
|
+
import re
|
|
4
|
+
import codecs
|
|
5
|
+
from decimal import Decimal, InvalidOperation
|
|
6
|
+
from datetime import datetime, date, time
|
|
7
|
+
from typing import Optional, Union, Callable
|
|
8
|
+
from velocity.misc.conv.iconv import (
|
|
9
|
+
none,
|
|
10
|
+
phone,
|
|
11
|
+
day_of_week,
|
|
12
|
+
date as date_func,
|
|
13
|
+
time as time_func,
|
|
14
|
+
timestamp,
|
|
15
|
+
email,
|
|
16
|
+
integer,
|
|
17
|
+
boolean,
|
|
18
|
+
rot13,
|
|
19
|
+
pointer,
|
|
20
|
+
money,
|
|
21
|
+
round_to,
|
|
22
|
+
decimal as decimal_func,
|
|
23
|
+
ein,
|
|
24
|
+
to_list,
|
|
25
|
+
title,
|
|
26
|
+
lower,
|
|
27
|
+
upper,
|
|
28
|
+
padding,
|
|
29
|
+
string,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TestIconvModule(unittest.TestCase):
|
|
34
|
+
|
|
35
|
+
def test_none(self):
|
|
36
|
+
self.assertIsNone(none(""))
|
|
37
|
+
self.assertIsNone(none("null"))
|
|
38
|
+
self.assertIsNone(none("None"))
|
|
39
|
+
self.assertIsNone(none("@NULL"))
|
|
40
|
+
self.assertEqual(none("value"), "value")
|
|
41
|
+
|
|
42
|
+
def test_phone(self):
|
|
43
|
+
self.assertEqual(phone("123-456-7890"), "1234567890")
|
|
44
|
+
self.assertEqual(phone("(123) 456-7890"), "1234567890")
|
|
45
|
+
self.assertIsNone(phone("12345"))
|
|
46
|
+
self.assertIsNone(phone(None))
|
|
47
|
+
self.assertIsNone(phone("None"))
|
|
48
|
+
|
|
49
|
+
def test_day_of_week(self):
|
|
50
|
+
self.assertEqual(day_of_week("Monday"), 1)
|
|
51
|
+
self.assertEqual(day_of_week("fri"), 5)
|
|
52
|
+
self.assertIsNone(day_of_week("Funday"))
|
|
53
|
+
self.assertIsNone(day_of_week(""))
|
|
54
|
+
self.assertIsNone(day_of_week(None))
|
|
55
|
+
|
|
56
|
+
def test_date(self):
|
|
57
|
+
self.assertEqual(date_func("2023-01-01"), date(2023, 1, 1))
|
|
58
|
+
self.assertIsNone(date_func("2023/01/01"))
|
|
59
|
+
self.assertIsNone(date_func(""))
|
|
60
|
+
self.assertIsNone(date_func(None))
|
|
61
|
+
self.assertEqual(date_func("01-02-2023", fmt="%d-%m-%Y"), date(2023, 2, 1))
|
|
62
|
+
|
|
63
|
+
def test_time(self):
|
|
64
|
+
self.assertEqual(time_func("23:59:59"), time(23, 59, 59))
|
|
65
|
+
self.assertIsNone(time_func("24:00:00"))
|
|
66
|
+
self.assertIsNone(time_func(""))
|
|
67
|
+
self.assertIsNone(time_func(None))
|
|
68
|
+
self.assertEqual(time_func("11:30 PM", fmt="%I:%M %p"), time(23, 30))
|
|
69
|
+
|
|
70
|
+
def test_timestamp(self):
|
|
71
|
+
self.assertEqual(
|
|
72
|
+
timestamp("01 Jan 23 12:34:56", fmt="%d %b %y %H:%M:%S"),
|
|
73
|
+
datetime(2023, 1, 1, 12, 34, 56),
|
|
74
|
+
)
|
|
75
|
+
self.assertIsNone(timestamp("Invalid date"))
|
|
76
|
+
self.assertIsNone(timestamp(""))
|
|
77
|
+
self.assertIsNone(timestamp(None))
|
|
78
|
+
|
|
79
|
+
def test_email(self):
|
|
80
|
+
self.assertEqual(email("Test@Example.com"), "test@example.com")
|
|
81
|
+
self.assertEqual(
|
|
82
|
+
email(" user.name+tag+sorting@example.com "),
|
|
83
|
+
"user.name+tag+sorting@example.com",
|
|
84
|
+
)
|
|
85
|
+
self.assertIsNone(email(None))
|
|
86
|
+
self.assertIsNone(email("None"))
|
|
87
|
+
with self.assertRaises(ValueError):
|
|
88
|
+
email("invalid-email")
|
|
89
|
+
|
|
90
|
+
def test_integer(self):
|
|
91
|
+
self.assertEqual(integer("123"), 123)
|
|
92
|
+
self.assertEqual(integer("-123"), -123)
|
|
93
|
+
self.assertEqual(integer("1,234"), 1234)
|
|
94
|
+
self.assertEqual(integer("$1,234.56"), 1234)
|
|
95
|
+
self.assertEqual(integer("abc123xyz"), 123)
|
|
96
|
+
with self.assertRaises(ValueError):
|
|
97
|
+
integer("abc")
|
|
98
|
+
|
|
99
|
+
def test_boolean(self):
|
|
100
|
+
self.assertFalse(boolean(""))
|
|
101
|
+
self.assertFalse(boolean("false"))
|
|
102
|
+
self.assertFalse(boolean("False"))
|
|
103
|
+
self.assertFalse(boolean("f"))
|
|
104
|
+
self.assertFalse(boolean("off"))
|
|
105
|
+
self.assertFalse(boolean("no"))
|
|
106
|
+
self.assertTrue(boolean("True"))
|
|
107
|
+
self.assertTrue(boolean("yes"))
|
|
108
|
+
self.assertTrue(boolean(True))
|
|
109
|
+
self.assertFalse(boolean(False))
|
|
110
|
+
|
|
111
|
+
def test_rot13(self):
|
|
112
|
+
self.assertEqual(rot13("Hello"), "Uryyb")
|
|
113
|
+
self.assertEqual(rot13("Uryyb"), "Hello")
|
|
114
|
+
self.assertEqual(rot13(""), "")
|
|
115
|
+
self.assertEqual(
|
|
116
|
+
rot13("Gur Dhvpx Oebja Sbk Whzcf Bire Gur Ynml Qbt."),
|
|
117
|
+
"The Quick Brown Fox Jumps Over The Lazy Dog.",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def test_pointer(self):
|
|
121
|
+
self.assertIsNone(pointer("@new"))
|
|
122
|
+
self.assertIsNone(pointer("@NULL"))
|
|
123
|
+
self.assertIsNone(pointer(None))
|
|
124
|
+
self.assertIsNone(pointer(""))
|
|
125
|
+
self.assertEqual(pointer("123"), 123)
|
|
126
|
+
self.assertEqual(pointer(456), 456)
|
|
127
|
+
with self.assertRaises(ValueError):
|
|
128
|
+
pointer("abc")
|
|
129
|
+
|
|
130
|
+
def test_money(self):
|
|
131
|
+
self.assertEqual(money("$1,234.56"), Decimal("1234.56"))
|
|
132
|
+
self.assertEqual(money("-$1,234.56"), Decimal("-1234.56"))
|
|
133
|
+
self.assertIsNone(money("None"))
|
|
134
|
+
self.assertIsNone(money(None))
|
|
135
|
+
with self.assertRaises(InvalidOperation):
|
|
136
|
+
money("Invalid")
|
|
137
|
+
|
|
138
|
+
def test_round_to(self):
|
|
139
|
+
func = round_to(2)
|
|
140
|
+
self.assertEqual(func("123.456"), Decimal("123.46"))
|
|
141
|
+
self.assertEqual(func(Decimal("123.451")), Decimal("123.45"))
|
|
142
|
+
self.assertIsNone(func(None))
|
|
143
|
+
self.assertEqual(round_to(2, "123.456"), Decimal("123.46"))
|
|
144
|
+
|
|
145
|
+
def test_decimal(self):
|
|
146
|
+
self.assertEqual(decimal_func("123.456"), Decimal("123.456"))
|
|
147
|
+
self.assertEqual(decimal_func("$1,234.56"), Decimal("1234.56"))
|
|
148
|
+
self.assertIsNone(decimal_func("None"))
|
|
149
|
+
self.assertIsNone(decimal_func(None))
|
|
150
|
+
with self.assertRaises(InvalidOperation):
|
|
151
|
+
decimal_func("Invalid")
|
|
152
|
+
|
|
153
|
+
def test_ein(self):
|
|
154
|
+
self.assertEqual(ein("12-3456789"), "123456789")
|
|
155
|
+
self.assertEqual(ein("123456789"), "123456789")
|
|
156
|
+
self.assertIsNone(ein("123-45-6789"))
|
|
157
|
+
self.assertIsNone(ein("12345678"))
|
|
158
|
+
self.assertIsNone(ein(None))
|
|
159
|
+
self.assertIsNone(ein("None"))
|
|
160
|
+
|
|
161
|
+
def test_to_list(self):
|
|
162
|
+
self.assertEqual(to_list("[1, 2, 3]"), [1, 2, 3])
|
|
163
|
+
self.assertEqual(to_list("['a', 'b', 'c']"), ["a", "b", "c"])
|
|
164
|
+
self.assertEqual(to_list("value"), ["value"])
|
|
165
|
+
self.assertEqual(to_list(["value1", "value2"]), ["value1", "value2"])
|
|
166
|
+
self.assertIsNone(to_list(None))
|
|
167
|
+
self.assertIsNone(to_list("None"))
|
|
168
|
+
# Test with potential security risk input
|
|
169
|
+
with self.assertRaises(SyntaxError):
|
|
170
|
+
to_list("__import__('os').system('echo dangerous')")
|
|
171
|
+
|
|
172
|
+
def test_title(self):
|
|
173
|
+
self.assertEqual(title("hello world"), "Hello World")
|
|
174
|
+
self.assertEqual(title("TEST"), "Test")
|
|
175
|
+
self.assertEqual(title(None), "")
|
|
176
|
+
self.assertEqual(title("None"), "")
|
|
177
|
+
|
|
178
|
+
def test_lower(self):
|
|
179
|
+
self.assertEqual(lower("Hello World"), "hello world")
|
|
180
|
+
self.assertEqual(lower("TEST"), "test")
|
|
181
|
+
self.assertEqual(lower(None), "")
|
|
182
|
+
self.assertEqual(lower("None"), "")
|
|
183
|
+
|
|
184
|
+
def test_upper(self):
|
|
185
|
+
self.assertEqual(upper("Hello World"), "HELLO WORLD")
|
|
186
|
+
self.assertEqual(upper("test"), "TEST")
|
|
187
|
+
self.assertEqual(upper(None), "")
|
|
188
|
+
self.assertEqual(upper("None"), "")
|
|
189
|
+
|
|
190
|
+
def test_padding(self):
|
|
191
|
+
pad_func = padding(5)
|
|
192
|
+
self.assertEqual(pad_func("1"), " 1")
|
|
193
|
+
self.assertEqual(pad_func("12345"), "12345")
|
|
194
|
+
self.assertEqual(pad_func("123456"), "123456")
|
|
195
|
+
self.assertIsNone(pad_func(None))
|
|
196
|
+
self.assertIsNone(pad_func(""))
|
|
197
|
+
pad_func_char = padding(5, "0")
|
|
198
|
+
self.assertEqual(pad_func_char("1"), "00001")
|
|
199
|
+
|
|
200
|
+
def test_string(self):
|
|
201
|
+
self.assertEqual(string("value"), "value")
|
|
202
|
+
self.assertIsNone(string(""))
|
|
203
|
+
self.assertEqual(string(None), None)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
if __name__ == "__main__":
|
|
207
|
+
unittest.main()
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import datetime
|
|
3
|
+
import decimal
|
|
4
|
+
from velocity.db.servers.postgres import (
|
|
5
|
+
make_where,
|
|
6
|
+
quote,
|
|
7
|
+
SQL,
|
|
8
|
+
) # Replace 'your_module' with the actual module name
|
|
9
|
+
from velocity.db.core.table import (
|
|
10
|
+
Query,
|
|
11
|
+
) # Adjust the import path according to your project structure
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestSQLModule(unittest.TestCase):
|
|
15
|
+
# Existing tests (as previously provided)
|
|
16
|
+
# ...
|
|
17
|
+
|
|
18
|
+
# New tests for complicated SQL edge cases
|
|
19
|
+
|
|
20
|
+
def test_make_where_with_nested_subquery(self):
|
|
21
|
+
sql = []
|
|
22
|
+
vals = []
|
|
23
|
+
subquery = Query("SELECT id FROM other_table WHERE value = %s", ("test",))
|
|
24
|
+
make_where({"id": subquery}, sql, vals)
|
|
25
|
+
self.assertEqual(
|
|
26
|
+
" ".join(sql), "WHERE id in (SELECT id FROM other_table WHERE value = %s)"
|
|
27
|
+
)
|
|
28
|
+
self.assertEqual(vals, ("test",))
|
|
29
|
+
|
|
30
|
+
def test_make_where_with_multiple_conditions(self):
|
|
31
|
+
sql = []
|
|
32
|
+
vals = []
|
|
33
|
+
where_conditions = [
|
|
34
|
+
("column1>", 5),
|
|
35
|
+
("column2<=", 10),
|
|
36
|
+
("column3%", "%value%"),
|
|
37
|
+
("column4!%", "%exclude%"),
|
|
38
|
+
]
|
|
39
|
+
make_where(where_conditions, sql, vals)
|
|
40
|
+
expected_sql = "WHERE column1 > %s AND column2 <= %s AND column3 ilike %s AND column4 not like %s"
|
|
41
|
+
self.assertEqual(" ".join(sql), expected_sql)
|
|
42
|
+
self.assertEqual(vals, [5, 10, "%value%", "%exclude%"])
|
|
43
|
+
|
|
44
|
+
def test_make_where_with_complex_operators(self):
|
|
45
|
+
sql = []
|
|
46
|
+
vals = []
|
|
47
|
+
make_where({"column1~": "^test.*", "column2!~*": ".*exclude$"}, sql, vals)
|
|
48
|
+
self.assertEqual(" ".join(sql), "WHERE column1 ~ %s AND column2 !~* %s")
|
|
49
|
+
self.assertEqual(vals, ["^test.*", ".*exclude$"])
|
|
50
|
+
|
|
51
|
+
def test_sql_select_with_complex_join(self):
|
|
52
|
+
sql_query, params = SQL.select(
|
|
53
|
+
columns=["A.id", "B.name", "C.value"],
|
|
54
|
+
table="tableA A",
|
|
55
|
+
inner_join={"tableB B": "A.b_id = B.id", "tableC C": "B.c_id = C.id"},
|
|
56
|
+
where={"A.status": "active", "C.value>": 100},
|
|
57
|
+
orderby="C.value DESC",
|
|
58
|
+
)
|
|
59
|
+
expected_sql = (
|
|
60
|
+
"SELECT A.id,B.name,C.value FROM tableA A "
|
|
61
|
+
"INNER JOIN tableB B ON A.b_id = B.id "
|
|
62
|
+
"INNER JOIN tableC C ON B.c_id = C.id "
|
|
63
|
+
"WHERE A.status = %s AND C.value > %s ORDER BY C.value DESC"
|
|
64
|
+
)
|
|
65
|
+
self.assertEqual(sql_query, expected_sql)
|
|
66
|
+
self.assertEqual(params, ("active", 100))
|
|
67
|
+
|
|
68
|
+
def test_sql_select_with_group_by_and_having(self):
|
|
69
|
+
sql_query, params = SQL.select(
|
|
70
|
+
columns="column1, COUNT(*)",
|
|
71
|
+
table="my_table",
|
|
72
|
+
groupby="column1",
|
|
73
|
+
having="COUNT(*) > 1",
|
|
74
|
+
orderby="column1 ASC",
|
|
75
|
+
)
|
|
76
|
+
expected_sql = "SELECT column1, COUNT(*) FROM my_table GROUP BY column1 HAVING COUNT(*) > 1 ORDER BY column1 ASC"
|
|
77
|
+
self.assertEqual(sql_query, expected_sql)
|
|
78
|
+
self.assertEqual(params, ())
|
|
79
|
+
|
|
80
|
+
def test_sql_insert_with_special_types(self):
|
|
81
|
+
sql_query, params = SQL.insert(
|
|
82
|
+
table="my_table",
|
|
83
|
+
data={
|
|
84
|
+
"int_column": 123,
|
|
85
|
+
"decimal_column": decimal.Decimal("123.456"),
|
|
86
|
+
"datetime_column": datetime.datetime(2023, 1, 1, 12, 0),
|
|
87
|
+
"bool_column": True,
|
|
88
|
+
"text_column": "Some text",
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
self.assertIn("INSERT INTO my_table", sql_query)
|
|
92
|
+
self.assertEqual(len(params), 5)
|
|
93
|
+
self.assertIsInstance(params[1], decimal.Decimal)
|
|
94
|
+
self.assertIsInstance(params[2], datetime.datetime)
|
|
95
|
+
self.assertIsInstance(params[3], bool)
|
|
96
|
+
|
|
97
|
+
def test_sql_update_with_excluded(self):
|
|
98
|
+
sql_query, params = SQL.update(
|
|
99
|
+
table="my_table",
|
|
100
|
+
data={"column1": "value1", "column2": "value2"},
|
|
101
|
+
pk={"id": 1},
|
|
102
|
+
excluded=True,
|
|
103
|
+
)
|
|
104
|
+
expected_sql = (
|
|
105
|
+
"UPDATE SET column1 = EXCLUDED.column1,column2 = EXCLUDED.column2"
|
|
106
|
+
)
|
|
107
|
+
self.assertEqual(sql_query, expected_sql)
|
|
108
|
+
self.assertEqual(params, ())
|
|
109
|
+
|
|
110
|
+
def test_sql_create_table_with_constraints(self):
|
|
111
|
+
sql_query, params = SQL.create_table(
|
|
112
|
+
name="public.test_table",
|
|
113
|
+
columns={
|
|
114
|
+
"id": "@@SERIAL PRIMARY KEY",
|
|
115
|
+
"name": str,
|
|
116
|
+
"age": int,
|
|
117
|
+
"email": "@@VARCHAR(255) UNIQUE",
|
|
118
|
+
"created_at": "@@TIMESTAMP DEFAULT CURRENT_TIMESTAMP",
|
|
119
|
+
},
|
|
120
|
+
drop=True,
|
|
121
|
+
)
|
|
122
|
+
self.assertIn("CREATE TABLE public.test_table", sql_query)
|
|
123
|
+
self.assertIn("id SERIAL PRIMARY KEY", sql_query)
|
|
124
|
+
self.assertIn("email VARCHAR(255) UNIQUE", sql_query)
|
|
125
|
+
self.assertIn("created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP", sql_query)
|
|
126
|
+
self.assertEqual(params, ())
|
|
127
|
+
|
|
128
|
+
def test_sql_create_foreign_key_with_schema(self):
|
|
129
|
+
sql_query, params = SQL.create_foreign_key(
|
|
130
|
+
table="schema.child_table",
|
|
131
|
+
columns="parent_id",
|
|
132
|
+
key_to_table="schema.parent_table",
|
|
133
|
+
key_to_columns="id",
|
|
134
|
+
schema="schema",
|
|
135
|
+
)
|
|
136
|
+
self.assertIn("ALTER TABLE schema.child_table ADD CONSTRAINT", sql_query)
|
|
137
|
+
self.assertIn("REFERENCES schema.parent_table (id);", sql_query)
|
|
138
|
+
self.assertEqual(params, ())
|
|
139
|
+
|
|
140
|
+
def test_sql_merge_with_complex_conflict_resolution(self):
|
|
141
|
+
sql_query, params = SQL.merge(
|
|
142
|
+
table="my_table",
|
|
143
|
+
data={"column1": "value1", "column2": "value2"},
|
|
144
|
+
pk={"id": 1},
|
|
145
|
+
on_conflict_do_nothing=False,
|
|
146
|
+
on_conflict_update=True,
|
|
147
|
+
)
|
|
148
|
+
self.assertIn("ON CONFLICT (id) DO UPDATE SET", sql_query)
|
|
149
|
+
self.assertIn("column1 = EXCLUDED.column1", sql_query)
|
|
150
|
+
self.assertEqual(params, ("value1", "value2"))
|
|
151
|
+
|
|
152
|
+
def test_sql_select_with_subquery_in_columns(self):
|
|
153
|
+
sql_query, params = SQL.select(
|
|
154
|
+
columns="id, (SELECT COUNT(*) FROM orders WHERE user_id = users.id) as order_count",
|
|
155
|
+
table="users",
|
|
156
|
+
where={"status": "active"},
|
|
157
|
+
)
|
|
158
|
+
expected_sql = (
|
|
159
|
+
"SELECT id, (SELECT COUNT(*) FROM orders WHERE user_id = users.id) as order_count "
|
|
160
|
+
"FROM users WHERE status = %s"
|
|
161
|
+
)
|
|
162
|
+
self.assertEqual(sql_query, expected_sql)
|
|
163
|
+
self.assertEqual(params, ("active",))
|
|
164
|
+
|
|
165
|
+
def test_make_where_with_special_characters_in_values(self):
|
|
166
|
+
sql = []
|
|
167
|
+
vals = []
|
|
168
|
+
make_where({"text_column": "O'Reilly"}, sql, vals)
|
|
169
|
+
self.assertEqual(" ".join(sql), "WHERE text_column = %s")
|
|
170
|
+
self.assertEqual(vals, ["O'Reilly"])
|
|
171
|
+
|
|
172
|
+
def test_sql_update_with_complex_where(self):
|
|
173
|
+
sql_query, params = SQL.update(
|
|
174
|
+
table="my_table",
|
|
175
|
+
data={"status": "inactive"},
|
|
176
|
+
pk={"last_login<": datetime.datetime.now(), "status": "active"},
|
|
177
|
+
)
|
|
178
|
+
expected_sql = (
|
|
179
|
+
"UPDATE my_table SET status = %s WHERE last_login < %s AND status = %s"
|
|
180
|
+
)
|
|
181
|
+
self.assertEqual(sql_query, expected_sql)
|
|
182
|
+
self.assertEqual(len(params), 3)
|
|
183
|
+
self.assertEqual(params[0], "inactive")
|
|
184
|
+
|
|
185
|
+
def test_sql_delete_with_subquery(self):
|
|
186
|
+
subquery = Query("SELECT id FROM inactive_users", ())
|
|
187
|
+
sql_query, params = SQL.delete(table="users", where={"id": subquery})
|
|
188
|
+
expected_sql = "DELETE FROM users WHERE id in (SELECT id FROM inactive_users)"
|
|
189
|
+
self.assertEqual(sql_query, expected_sql)
|
|
190
|
+
self.assertEqual(params, ())
|
|
191
|
+
|
|
192
|
+
def test_sql_select_with_offset_and_limit(self):
|
|
193
|
+
sql_query, params = SQL.select(columns="*", table="my_table", start=10, qty=20)
|
|
194
|
+
expected_sql = "SELECT * FROM my_table OFFSET 10 ROWS FETCH NEXT 20 ROWS ONLY"
|
|
195
|
+
self.assertEqual(sql_query, expected_sql)
|
|
196
|
+
self.assertEqual(params, ())
|
|
197
|
+
|
|
198
|
+
def test_sql_create_index_with_trigram(self):
|
|
199
|
+
sql_query, params = SQL.create_index(
|
|
200
|
+
table="my_table", columns="text_column", trigram="GIN"
|
|
201
|
+
)
|
|
202
|
+
self.assertIn("USING GIN", sql_query)
|
|
203
|
+
self.assertIn("(text_column) gin_trgm_ops", sql_query)
|
|
204
|
+
self.assertEqual(params, ())
|
|
205
|
+
|
|
206
|
+
def test_sql_alter_table_add_column(self):
|
|
207
|
+
sql_query, params = SQL.alter_add(
|
|
208
|
+
table="my_table", columns={"new_column": str}, null_allowed=False
|
|
209
|
+
)
|
|
210
|
+
expected_sql = "ALTER TABLE my_table ADD new_column TEXT NOT NULL;"
|
|
211
|
+
self.assertEqual(sql_query.strip(), expected_sql.strip())
|
|
212
|
+
self.assertEqual(params, ())
|
|
213
|
+
|
|
214
|
+
def test_sql_alter_table_drop_column(self):
|
|
215
|
+
sql_query, params = SQL.alter_drop(
|
|
216
|
+
table="my_table", columns={"old_column": None}
|
|
217
|
+
)
|
|
218
|
+
expected_sql = "ALTER TABLE my_table DROP COLUMN old_column"
|
|
219
|
+
self.assertIn(expected_sql, sql_query)
|
|
220
|
+
self.assertEqual(params, ())
|
|
221
|
+
|
|
222
|
+
def test_sql_alter_column_type(self):
|
|
223
|
+
sql_query, params = SQL.alter_column_by_type(
|
|
224
|
+
table="my_table",
|
|
225
|
+
column="int_column",
|
|
226
|
+
value=decimal.Decimal("0.0"),
|
|
227
|
+
nullable=False,
|
|
228
|
+
)
|
|
229
|
+
expected_sql = (
|
|
230
|
+
"ALTER TABLE my_table ALTER COLUMN int_column TYPE NUMERIC(19, 6) "
|
|
231
|
+
"USING int_column::NUMERIC NOT NULL"
|
|
232
|
+
)
|
|
233
|
+
self.assertEqual(sql_query.strip(), expected_sql.strip())
|
|
234
|
+
self.assertEqual(params, ())
|
|
235
|
+
|
|
236
|
+
def test_sql_alter_column_with_sql(self):
|
|
237
|
+
sql_query, params = SQL.alter_column_by_sql(
|
|
238
|
+
table="my_table", column="text_column", value="SET DEFAULT %s"
|
|
239
|
+
)
|
|
240
|
+
expected_sql = "ALTER TABLE my_table ALTER COLUMN text_column SET DEFAULT %s"
|
|
241
|
+
self.assertEqual(sql_query, expected_sql)
|
|
242
|
+
self.assertEqual(params, ())
|
|
243
|
+
|
|
244
|
+
def test_sql_duplicate_rows_detection(self):
|
|
245
|
+
sql_query, params = SQL.duplicate_rows(
|
|
246
|
+
table="my_table", columns=["column1", "column2"], where={"status": "active"}
|
|
247
|
+
)
|
|
248
|
+
expected_sql = (
|
|
249
|
+
"SELECT column1,column2 FROM my_table WHERE status = %s "
|
|
250
|
+
"GROUP BY column1,column2 HAVING count(*) > 2"
|
|
251
|
+
)
|
|
252
|
+
self.assertEqual(sql_query, expected_sql)
|
|
253
|
+
self.assertEqual(params, ("active",))
|
|
254
|
+
|
|
255
|
+
def test_sql_missing_ids(self):
|
|
256
|
+
sql_query, params = SQL.missing(
|
|
257
|
+
table="my_table",
|
|
258
|
+
list=[1, 2, 3, 4, 5],
|
|
259
|
+
column="id",
|
|
260
|
+
where={"status": "active"},
|
|
261
|
+
)
|
|
262
|
+
expected_sql = (
|
|
263
|
+
"SELECT * FROM UNNEST('{1,2,3,4,5}'::int[]) id EXCEPT ALL "
|
|
264
|
+
"SELECT id FROM my_table WHERE status = %s"
|
|
265
|
+
)
|
|
266
|
+
self.assertEqual(sql_query, expected_sql)
|
|
267
|
+
self.assertEqual(params, ("active",))
|
|
268
|
+
|
|
269
|
+
# Additional edge cases can be added here
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
if __name__ == "__main__":
|
|
273
|
+
unittest.main()
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
import datetime
|
|
3
|
-
from velocity.misc.conv.iconv import (
|
|
4
|
-
none,
|
|
5
|
-
phone,
|
|
6
|
-
day_of_week,
|
|
7
|
-
date,
|
|
8
|
-
time,
|
|
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 TestIconvFunctions(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("1234567890"), "(123) 456-7890")
|
|
39
|
-
self.assertEqual(phone("invalid"), "")
|
|
40
|
-
self.assertEqual(phone(None), "")
|
|
41
|
-
|
|
42
|
-
def test_day_of_week(self):
|
|
43
|
-
self.assertEqual(day_of_week(1), "Monday")
|
|
44
|
-
self.assertEqual(day_of_week("2"), "Tuesday")
|
|
45
|
-
self.assertEqual(day_of_week(5, abbrev=True), "Fri")
|
|
46
|
-
self.assertEqual(day_of_week([1, 2, 5], abbrev=True), "Mon,Tue,Fri")
|
|
47
|
-
self.assertEqual(day_of_week("invalid"), "")
|
|
48
|
-
|
|
49
|
-
def test_date(self):
|
|
50
|
-
self.assertEqual(date(datetime.datetime(2023, 1, 1)), "2023-01-01")
|
|
51
|
-
self.assertEqual(date(datetime.date(2023, 1, 1)), "2023-01-01")
|
|
52
|
-
self.assertEqual(date("not a date"), "not a date")
|
|
53
|
-
|
|
54
|
-
def test_time(self):
|
|
55
|
-
self.assertEqual(time(datetime.datetime(2023, 1, 1, 12, 30, 45)), "12:30:45")
|
|
56
|
-
self.assertEqual(time(datetime.time(12, 30, 45)), "12:30:45")
|
|
57
|
-
self.assertEqual(time("invalid"), "invalid")
|
|
58
|
-
|
|
59
|
-
def test_timestamp(self):
|
|
60
|
-
self.assertEqual(
|
|
61
|
-
timestamp(datetime.datetime(2023, 1, 1, 12, 30, 45)),
|
|
62
|
-
"Sun Jan 1 12:30:45 2023",
|
|
63
|
-
)
|
|
64
|
-
self.assertEqual(timestamp("invalid"), "invalid")
|
|
65
|
-
|
|
66
|
-
def test_email(self):
|
|
67
|
-
self.assertEqual(email("EXAMPLE@domain.com"), "example@domain.com")
|
|
68
|
-
self.assertEqual(email("None"), "")
|
|
69
|
-
self.assertEqual(email(None), "")
|
|
70
|
-
|
|
71
|
-
def test_pointer(self):
|
|
72
|
-
self.assertEqual(pointer("123"), 123)
|
|
73
|
-
self.assertEqual(pointer("invalid"), "")
|
|
74
|
-
self.assertEqual(pointer(None), "")
|
|
75
|
-
|
|
76
|
-
def test_rot13(self):
|
|
77
|
-
self.assertEqual(rot13("hello"), "uryyb")
|
|
78
|
-
self.assertEqual(rot13("uryyb"), "hello")
|
|
79
|
-
|
|
80
|
-
def test_boolean(self):
|
|
81
|
-
self.assertFalse(boolean("false"))
|
|
82
|
-
self.assertTrue(boolean("true"))
|
|
83
|
-
self.assertTrue(boolean(True))
|
|
84
|
-
self.assertFalse(boolean(False))
|
|
85
|
-
|
|
86
|
-
def test_money(self):
|
|
87
|
-
self.assertEqual(money("1234.5"), "$1,234.50")
|
|
88
|
-
self.assertEqual(money("-1234.56"), "-$1,234.56")
|
|
89
|
-
self.assertEqual(money("None"), "")
|
|
90
|
-
self.assertEqual(money(None), "")
|
|
91
|
-
|
|
92
|
-
def test_round_to(self):
|
|
93
|
-
self.assertEqual(round_to(2, "123.456"), "123.46")
|
|
94
|
-
self.assertEqual(round_to(1, "123.456"), "123.5")
|
|
95
|
-
round_func = round_to(1)
|
|
96
|
-
self.assertEqual(round_func("123.45"), "123.5")
|
|
97
|
-
|
|
98
|
-
def test_ein(self):
|
|
99
|
-
self.assertEqual(ein("123456789"), "12-3456789")
|
|
100
|
-
self.assertEqual(ein("12-3456789"), "12-3456789")
|
|
101
|
-
self.assertEqual(ein("invalid"), "")
|
|
102
|
-
|
|
103
|
-
def test_to_list(self):
|
|
104
|
-
self.assertEqual(to_list("[1, 2, 3]"), [1, 2, 3])
|
|
105
|
-
self.assertEqual(to_list("single"), ["single"])
|
|
106
|
-
self.assertEqual(to_list(["already", "a", "list"]), ["already", "a", "list"])
|
|
107
|
-
self.assertIsNone(to_list("None"))
|
|
108
|
-
|
|
109
|
-
def test_title(self):
|
|
110
|
-
self.assertEqual(title("hello world"), "Hello World")
|
|
111
|
-
self.assertEqual(title("None"), "")
|
|
112
|
-
self.assertEqual(title(None), "")
|
|
113
|
-
|
|
114
|
-
def test_lower(self):
|
|
115
|
-
self.assertEqual(lower("HELLO"), "hello")
|
|
116
|
-
self.assertEqual(lower("None"), "")
|
|
117
|
-
self.assertEqual(lower(None), "")
|
|
118
|
-
|
|
119
|
-
def test_upper(self):
|
|
120
|
-
self.assertEqual(upper("hello"), "HELLO")
|
|
121
|
-
self.assertEqual(upper("None"), "")
|
|
122
|
-
self.assertEqual(upper(None), "")
|
|
123
|
-
|
|
124
|
-
def test_padding(self):
|
|
125
|
-
pad_func = padding(10, " ")
|
|
126
|
-
self.assertEqual(pad_func("123"), " 123")
|
|
127
|
-
self.assertEqual(pad_func(None), "")
|
|
128
|
-
self.assertEqual(padding(5, "0")("12"), "00012")
|
|
129
|
-
|
|
130
|
-
def test_pprint(self):
|
|
131
|
-
self.assertEqual(pprint("[1, 2, 3]"), "[1, 2, 3]")
|
|
132
|
-
self.assertEqual(pprint("invalid"), "invalid")
|
|
133
|
-
|
|
134
|
-
def test_string(self):
|
|
135
|
-
self.assertEqual(string("text"), "text")
|
|
136
|
-
self.assertEqual(string(None), "")
|
|
137
|
-
self.assertEqual(string(""), "")
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if __name__ == "__main__":
|
|
141
|
-
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/aws/handlers/lambda_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|