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.

Files changed (57) hide show
  1. {velocity_python-0.0.32 → velocity_python-0.0.34}/PKG-INFO +1 -1
  2. {velocity_python-0.0.32 → velocity_python-0.0.34}/pyproject.toml +1 -1
  3. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/decorators.py +2 -0
  5. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/engine.py +3 -0
  6. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/table.py +8 -2
  7. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/transaction.py +13 -0
  8. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/servers/postgres.py +1 -1
  9. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/conv/iconv.py +5 -1
  10. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/conv/oconv.py +54 -24
  11. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity_python.egg-info/PKG-INFO +1 -1
  12. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity_python.egg-info/SOURCES.txt +2 -0
  13. velocity_python-0.0.34/tests/test_foreign_key_handling.py +169 -0
  14. velocity_python-0.0.34/tests/test_iconv.py +207 -0
  15. {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_postgres.py +2 -1
  16. velocity_python-0.0.34/tests/test_postgres_advanced.py +273 -0
  17. velocity_python-0.0.32/tests/test_iconv.py +0 -141
  18. {velocity_python-0.0.32 → velocity_python-0.0.34}/LICENSE +0 -0
  19. {velocity_python-0.0.32 → velocity_python-0.0.34}/README.md +0 -0
  20. {velocity_python-0.0.32 → velocity_python-0.0.34}/setup.cfg +0 -0
  21. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/aws/__init__.py +0 -0
  22. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/aws/handlers/__init__.py +0 -0
  23. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/aws/handlers/context.py +0 -0
  24. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  25. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/aws/handlers/response.py +0 -0
  26. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  27. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/__init__.py +0 -0
  28. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/__init__.py +0 -0
  29. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/column.py +0 -0
  30. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/database.py +0 -0
  31. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/exceptions.py +0 -0
  32. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/result.py +0 -0
  33. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/row.py +0 -0
  34. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/core/sequence.py +0 -0
  35. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/servers/__init__.py +0 -0
  36. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/servers/mysql.py +0 -0
  37. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/servers/sqlite.py +0 -0
  38. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/db/servers/sqlserver.py +0 -0
  39. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/__init__.py +0 -0
  40. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/conv/__init__.py +0 -0
  41. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/db.py +0 -0
  42. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/export.py +0 -0
  43. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/format.py +0 -0
  44. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/mail.py +0 -0
  45. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/merge.py +0 -0
  46. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity/misc/timer.py +0 -0
  47. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  48. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity_python.egg-info/requires.txt +0 -0
  49. {velocity_python-0.0.32 → velocity_python-0.0.34}/src/velocity_python.egg-info/top_level.txt +0 -0
  50. {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_db.py +0 -0
  51. {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_email_processing.py +0 -0
  52. {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_format.py +0 -0
  53. {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_merge.py +0 -0
  54. {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_oconv.py +0 -0
  55. {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_response.py +0 -0
  56. {velocity_python-0.0.32 → velocity_python-0.0.34}/tests/test_spreadsheet_functions.py +0 -0
  57. {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.32
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "velocity-python"
3
- version = "0.0.32"
3
+ version = "0.0.34"
4
4
  authors = [
5
5
  { name="Paul Perez", email="pperez@codeclubs.org" },
6
6
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.0.32"
1
+ __version__ = version = "0.0.34"
2
2
 
3
3
  from . import aws
4
4
  from . import db
@@ -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(str):
13
- pass
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
@@ -445,7 +445,7 @@ class SQL(object):
445
445
  trigger = "".format(name)
446
446
  sql = []
447
447
  if drop:
448
- sql.append(cls.drop_table(fqtn))
448
+ sql.append(cls.drop_table(fqtn)[0])
449
449
  sql.append(
450
450
  """
451
451
  CREATE TABLE {0} (
@@ -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
- return int(re.sub(r"[^0-9\.-]", "", data))
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
- from datetime import datetime, date, time
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 = {1: "Mon", 2: "Tue", 3: "Wed", 4: "Thu", 5: "Fri", 6: "Sat", 7: "Sun"}
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(data: Union[datetime, date, str], fmt: str = "%Y-%m-%d") -> str:
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 data.strftime(fmt) if isinstance(data, (datetime, date)) else str(data)
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 data.strftime(fmt) if isinstance(data, (datetime, time)) else str(data)
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 string as currency."""
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
- cleaned_data = re.sub(r"[^0-9\.-]", "", str(data))
96
- return f"${decimal.Decimal(cleaned_data):,.2f}"
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
- cleaned_value = re.sub(r"[^0-9\.-]", "", str(value))
106
- return (
107
- f"{decimal.Decimal(cleaned_value):.{precision}f}" if cleaned_value else "0"
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) and data.startswith("["):
129
- try:
130
- return eval(data) # Be cautious with eval; only use if data is trusted
131
- except (SyntaxError, NameError):
132
- return None
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
- return pformat(eval(data))
164
- except (SyntaxError, NameError):
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.32
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()
@@ -1,5 +1,6 @@
1
1
  import unittest
2
- from your_module import (
2
+ import decimal
3
+ from velocity.db.servers.postgres import (
3
4
  make_where,
4
5
  quote,
5
6
  SQL,
@@ -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()