velocity-python 0.0.131__py3-none-any.whl → 0.0.134__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.

Files changed (88) hide show
  1. velocity/__init__.py +1 -1
  2. velocity/app/tests/__init__.py +1 -0
  3. velocity/app/tests/test_email_processing.py +112 -0
  4. velocity/app/tests/test_payment_profile_sorting.py +191 -0
  5. velocity/app/tests/test_spreadsheet_functions.py +124 -0
  6. velocity/aws/tests/__init__.py +1 -0
  7. velocity/aws/tests/test_lambda_handler_json_serialization.py +120 -0
  8. velocity/aws/tests/test_response.py +163 -0
  9. velocity/db/core/decorators.py +20 -3
  10. velocity/db/core/engine.py +33 -7
  11. velocity/db/exceptions.py +7 -0
  12. velocity/db/servers/base/__init__.py +9 -0
  13. velocity/db/servers/base/initializer.py +70 -0
  14. velocity/db/servers/base/operators.py +98 -0
  15. velocity/db/servers/base/sql.py +503 -0
  16. velocity/db/servers/base/types.py +135 -0
  17. velocity/db/servers/mysql/__init__.py +73 -0
  18. velocity/db/servers/mysql/operators.py +54 -0
  19. velocity/db/servers/{mysql_reserved.py → mysql/reserved.py} +2 -14
  20. velocity/db/servers/mysql/sql.py +569 -0
  21. velocity/db/servers/mysql/types.py +107 -0
  22. velocity/db/servers/postgres/__init__.py +52 -2
  23. velocity/db/servers/postgres/operators.py +34 -0
  24. velocity/db/servers/postgres/sql.py +4 -3
  25. velocity/db/servers/postgres/types.py +88 -2
  26. velocity/db/servers/sqlite/__init__.py +61 -0
  27. velocity/db/servers/sqlite/operators.py +52 -0
  28. velocity/db/servers/sqlite/reserved.py +20 -0
  29. velocity/db/servers/sqlite/sql.py +530 -0
  30. velocity/db/servers/sqlite/types.py +92 -0
  31. velocity/db/servers/sqlserver/__init__.py +73 -0
  32. velocity/db/servers/sqlserver/operators.py +47 -0
  33. velocity/db/servers/sqlserver/reserved.py +32 -0
  34. velocity/db/servers/sqlserver/sql.py +625 -0
  35. velocity/db/servers/sqlserver/types.py +114 -0
  36. velocity/db/tests/__init__.py +1 -0
  37. velocity/db/tests/common_db_test.py +0 -0
  38. velocity/db/tests/postgres/__init__.py +1 -0
  39. velocity/db/tests/postgres/common.py +49 -0
  40. velocity/db/tests/postgres/test_column.py +29 -0
  41. velocity/db/tests/postgres/test_connections.py +25 -0
  42. velocity/db/tests/postgres/test_database.py +21 -0
  43. velocity/db/tests/postgres/test_engine.py +205 -0
  44. velocity/db/tests/postgres/test_general_usage.py +88 -0
  45. velocity/db/tests/postgres/test_imports.py +8 -0
  46. velocity/db/tests/postgres/test_result.py +19 -0
  47. velocity/db/tests/postgres/test_row.py +137 -0
  48. velocity/db/tests/postgres/test_schema_locking.py +335 -0
  49. velocity/db/tests/postgres/test_schema_locking_unit.py +115 -0
  50. velocity/db/tests/postgres/test_sequence.py +34 -0
  51. velocity/db/tests/postgres/test_table.py +101 -0
  52. velocity/db/tests/postgres/test_transaction.py +106 -0
  53. velocity/db/tests/sql/__init__.py +1 -0
  54. velocity/db/tests/sql/common.py +177 -0
  55. velocity/db/tests/sql/test_postgres_select_advanced.py +285 -0
  56. velocity/db/tests/sql/test_postgres_select_variances.py +517 -0
  57. velocity/db/tests/test_cursor_rowcount_fix.py +150 -0
  58. velocity/db/tests/test_db_utils.py +221 -0
  59. velocity/db/tests/test_postgres.py +212 -0
  60. velocity/db/tests/test_postgres_unchanged.py +81 -0
  61. velocity/db/tests/test_process_error_robustness.py +292 -0
  62. velocity/db/tests/test_result_caching.py +279 -0
  63. velocity/db/tests/test_result_sql_aware.py +117 -0
  64. velocity/db/tests/test_row_get_missing_column.py +72 -0
  65. velocity/db/tests/test_schema_locking_initializers.py +226 -0
  66. velocity/db/tests/test_schema_locking_simple.py +97 -0
  67. velocity/db/tests/test_sql_builder.py +165 -0
  68. velocity/db/tests/test_tablehelper.py +486 -0
  69. velocity/misc/tests/__init__.py +1 -0
  70. velocity/misc/tests/test_db.py +90 -0
  71. velocity/misc/tests/test_fix.py +78 -0
  72. velocity/misc/tests/test_format.py +64 -0
  73. velocity/misc/tests/test_iconv.py +203 -0
  74. velocity/misc/tests/test_merge.py +82 -0
  75. velocity/misc/tests/test_oconv.py +144 -0
  76. velocity/misc/tests/test_original_error.py +52 -0
  77. velocity/misc/tests/test_timer.py +74 -0
  78. {velocity_python-0.0.131.dist-info → velocity_python-0.0.134.dist-info}/METADATA +1 -1
  79. velocity_python-0.0.134.dist-info/RECORD +125 -0
  80. velocity/db/servers/mysql.py +0 -640
  81. velocity/db/servers/sqlite.py +0 -968
  82. velocity/db/servers/sqlite_reserved.py +0 -208
  83. velocity/db/servers/sqlserver.py +0 -921
  84. velocity/db/servers/sqlserver_reserved.py +0 -314
  85. velocity_python-0.0.131.dist-info/RECORD +0 -62
  86. {velocity_python-0.0.131.dist-info → velocity_python-0.0.134.dist-info}/WHEEL +0 -0
  87. {velocity_python-0.0.131.dist-info → velocity_python-0.0.134.dist-info}/licenses/LICENSE +0 -0
  88. {velocity_python-0.0.131.dist-info → velocity_python-0.0.134.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,114 @@
1
+ import decimal
2
+ import datetime
3
+ from ..base.types import BaseTypes
4
+
5
+
6
+ class TYPES(BaseTypes):
7
+ """
8
+ SQL Server-specific type mapping implementation.
9
+ """
10
+
11
+ TEXT = "NVARCHAR(MAX)"
12
+ VARCHAR = "VARCHAR"
13
+ NVARCHAR = "NVARCHAR"
14
+ INTEGER = "INT"
15
+ BIGINT = "BIGINT"
16
+ SMALLINT = "SMALLINT"
17
+ TINYINT = "TINYINT"
18
+ NUMERIC = "DECIMAL"
19
+ DECIMAL = "DECIMAL"
20
+ FLOAT = "FLOAT"
21
+ REAL = "REAL"
22
+ MONEY = "MONEY"
23
+ DATETIME = "DATETIME"
24
+ DATETIME2 = "DATETIME2"
25
+ DATE = "DATE"
26
+ TIME = "TIME"
27
+ TIMESTAMP = "ROWVERSION"
28
+ BOOLEAN = "BIT"
29
+ BINARY = "VARBINARY(MAX)"
30
+ UNIQUEIDENTIFIER = "UNIQUEIDENTIFIER"
31
+
32
+ @classmethod
33
+ def get_type(cls, v):
34
+ """
35
+ Returns a suitable SQL type string for a Python value/object (SQL Server).
36
+ """
37
+ is_special, special_val = cls._handle_special_values(v)
38
+ if is_special:
39
+ return special_val
40
+
41
+ if isinstance(v, str) or v is str:
42
+ return cls.TEXT
43
+ if isinstance(v, bool) or v is bool:
44
+ return cls.BOOLEAN
45
+ if isinstance(v, int) or v is int:
46
+ return cls.BIGINT
47
+ if isinstance(v, float) or v is float:
48
+ return f"{cls.DECIMAL}(19, 6)"
49
+ if isinstance(v, decimal.Decimal) or v is decimal.Decimal:
50
+ return f"{cls.DECIMAL}(19, 6)"
51
+ if isinstance(v, datetime.datetime) or v is datetime.datetime:
52
+ return cls.DATETIME2
53
+ if isinstance(v, datetime.date) or v is datetime.date:
54
+ return cls.DATE
55
+ if isinstance(v, datetime.time) or v is datetime.time:
56
+ return cls.TIME
57
+ if isinstance(v, bytes) or v is bytes:
58
+ return cls.BINARY
59
+ return cls.TEXT
60
+
61
+ @classmethod
62
+ def get_conv(cls, v):
63
+ """
64
+ Returns a base SQL type for expression usage (SQL Server).
65
+ """
66
+ is_special, special_val = cls._handle_special_values(v)
67
+ if is_special:
68
+ return special_val
69
+
70
+ if isinstance(v, str) or v is str:
71
+ return cls.NVARCHAR
72
+ if isinstance(v, bool) or v is bool:
73
+ return cls.BOOLEAN
74
+ if isinstance(v, int) or v is int:
75
+ return cls.BIGINT
76
+ if isinstance(v, float) or v is float:
77
+ return cls.DECIMAL
78
+ if isinstance(v, decimal.Decimal) or v is decimal.Decimal:
79
+ return cls.DECIMAL
80
+ if isinstance(v, datetime.datetime) or v is datetime.datetime:
81
+ return cls.DATETIME2
82
+ if isinstance(v, datetime.date) or v is datetime.date:
83
+ return cls.DATE
84
+ if isinstance(v, datetime.time) or v is datetime.time:
85
+ return cls.TIME
86
+ if isinstance(v, bytes) or v is bytes:
87
+ return cls.BINARY
88
+ return cls.NVARCHAR
89
+
90
+ @classmethod
91
+ def py_type(cls, v):
92
+ """
93
+ Returns the Python type that corresponds to an SQL type string (SQL Server).
94
+ """
95
+ v = str(v).upper()
96
+ if v in (cls.INTEGER, cls.SMALLINT, cls.BIGINT, cls.TINYINT):
97
+ return int
98
+ if v in (cls.NUMERIC, cls.DECIMAL, cls.MONEY) or "DECIMAL" in v:
99
+ return decimal.Decimal
100
+ if v in (cls.FLOAT, cls.REAL):
101
+ return float
102
+ if v in (cls.TEXT, cls.VARCHAR, cls.NVARCHAR) or "VARCHAR" in v or "CHAR" in v:
103
+ return str
104
+ if v == cls.BOOLEAN or v == "BIT":
105
+ return bool
106
+ if v == cls.DATE:
107
+ return datetime.date
108
+ if v == cls.TIME:
109
+ return datetime.time
110
+ if v in (cls.DATETIME, cls.DATETIME2):
111
+ return datetime.datetime
112
+ if v == cls.BINARY or "BINARY" in v:
113
+ return bytes
114
+ raise Exception(f"Unmapped SQL Server type {v}")
@@ -0,0 +1 @@
1
+ # Database module tests
File without changes
@@ -0,0 +1 @@
1
+ # PostgreSQL tests
@@ -0,0 +1,49 @@
1
+ import unittest
2
+ from velocity.db.servers import postgres
3
+ import env
4
+ env.set()
5
+
6
+ test_db = "test_db_postgres"
7
+ engine = postgres.initialize(database=test_db)
8
+
9
+
10
+ class CommonPostgresTest(unittest.TestCase):
11
+ """
12
+ Base test class for PostgreSQL tests following the common pattern.
13
+ All PostgreSQL tests should inherit from this class.
14
+ """
15
+
16
+ @classmethod
17
+ def setUpClass(cls):
18
+ """Set up the test database and create any common tables."""
19
+ @engine.transaction
20
+ def setup(tx):
21
+ tx.switch_to_database("postgres")
22
+ tx.execute(f"drop database if exists {test_db}", single=True)
23
+
24
+ # Create the test database
25
+ db = tx.database(test_db)
26
+ if not db.exists():
27
+ db.create()
28
+ db.switch()
29
+
30
+ # Call subclass-specific table creation with commit
31
+ if hasattr(cls, 'create_test_tables'):
32
+ cls.create_test_tables(tx)
33
+
34
+ setup()
35
+
36
+ @classmethod
37
+ def tearDownClass(cls):
38
+ """Clean up the test database."""
39
+ @engine.transaction
40
+ def cleanup(tx):
41
+ tx.switch_to_database("postgres")
42
+ tx.execute(f"drop database if exists {test_db}", single=True)
43
+
44
+ cleanup()
45
+
46
+ @classmethod
47
+ def create_test_tables(cls, tx):
48
+ """Override this method in subclasses to create test-specific tables."""
49
+ pass
@@ -0,0 +1,29 @@
1
+ import unittest
2
+ from velocity.db.core.column import Column
3
+ from velocity.db.exceptions import DbColumnMissingError
4
+ from .common import CommonPostgresTest, engine, test_db
5
+
6
+
7
+ @engine.transaction
8
+ @engine.transaction
9
+ class TestColumn(CommonPostgresTest):
10
+
11
+ @classmethod
12
+ def create_test_tables(cls, tx):
13
+ """Create test tables for column tests."""
14
+ tx.table("mock_table").create(
15
+ columns={
16
+ "column1": int,
17
+ "column2": str,
18
+ "column3": str,
19
+ }
20
+ )
21
+
22
+ def test_init(self, tx):
23
+ column = tx.table("mock_table").column("column1")
24
+ self.assertIsInstance(column, Column)
25
+ self.assertEqual(column.name, "column1")
26
+
27
+
28
+ if __name__ == "__main__":
29
+ unittest.main()
@@ -0,0 +1,25 @@
1
+ import unittest
2
+ from .common import CommonPostgresTest, engine, test_db
3
+
4
+
5
+ @engine.transaction
6
+ @engine.transaction
7
+ class TestConnections(CommonPostgresTest):
8
+
9
+ @classmethod
10
+ def create_test_tables(cls, tx):
11
+ """Create test tables for connection tests."""
12
+ tx.table("mock_table").create(
13
+ columns={
14
+ "column1": int,
15
+ "column2": str,
16
+ "column3": str,
17
+ }
18
+ )
19
+ def test_init(self, tx):
20
+ # Test the initialization of the Database object
21
+ assert tx.table("mock_table").exists()
22
+
23
+
24
+ if __name__ == "__main__":
25
+ unittest.main()
@@ -0,0 +1,21 @@
1
+ import unittest
2
+ from velocity.db.core.database import Database
3
+ from .common import CommonPostgresTest, engine, test_db
4
+
5
+
6
+ @engine.transaction
7
+ @engine.transaction
8
+ class TestDatabase(CommonPostgresTest):
9
+
10
+ @classmethod
11
+ def create_test_tables(cls, tx):
12
+ """No special tables needed for database tests."""
13
+ pass
14
+
15
+ def test_init(self, tx):
16
+ # Test the initialization of the Database object
17
+ tx.database
18
+
19
+
20
+ if __name__ == "__main__":
21
+ unittest.main()
@@ -0,0 +1,205 @@
1
+ import os
2
+ import datetime
3
+ import unittest
4
+ from velocity.db.core.engine import Engine
5
+ from velocity.db.core.transaction import Transaction
6
+ from .common import CommonPostgresTest, engine, test_db
7
+
8
+
9
+ @engine.transaction
10
+ class TestEngine(CommonPostgresTest):
11
+ @classmethod
12
+ def create_test_tables(cls, tx):
13
+ """No special tables needed for engine tests."""
14
+ pass
15
+
16
+ def test_engine_init(self, tx):
17
+ import psycopg2
18
+ from velocity.db.servers.postgres import SQL
19
+
20
+ # Test the engine instance from common test
21
+ assert engine.sql == SQL
22
+ assert engine.driver == psycopg2
23
+
24
+ def test_engine_attributes(self, tx):
25
+ import psycopg2
26
+ from velocity.db.servers.postgres import SQL
27
+
28
+ # Test private attributes
29
+ assert engine._Engine__sql == SQL
30
+ assert engine._Engine__driver == psycopg2
31
+
32
+ def test_connect(self, tx):
33
+ import psycopg2
34
+
35
+ assert engine.connect() != None
36
+ conn = engine.connect()
37
+ self.assertIsInstance(conn, psycopg2.extensions.connection)
38
+
39
+ def test_other_stuff(self, tx):
40
+ assert engine.version[:10] == "PostgreSQL"
41
+
42
+ timestamp = engine.timestamp
43
+ self.assertIsInstance(timestamp, datetime.datetime)
44
+ assert engine.user == os.environ["DBUser"]
45
+ assert test_db in engine.databases
46
+ assert "public" in engine.schemas
47
+ assert "information_schema" in engine.schemas
48
+ assert "public" == engine.current_schema
49
+ assert engine.current_database == test_db
50
+ assert [] == engine.views
51
+ assert [] == engine.tables
52
+
53
+ def test_process_error(self, tx):
54
+ local_engine = Engine(None, None, None) # Replace None with appropriate arguments
55
+ with self.assertRaises(
56
+ Exception
57
+ ): # Replace Exception with the specific exception raised by process_error
58
+ local_engine.process_error(sql_stmt=None, sql_params=None)
59
+ # Add additional assertions as needed
60
+
61
+ def test_transaction_injection_1(self, tx):
62
+ @engine.transaction
63
+ def function():
64
+ pass
65
+
66
+ function()
67
+
68
+ @engine.transaction
69
+ def function(_tx):
70
+ pass
71
+
72
+ with self.assertRaises(NameError):
73
+ function()
74
+
75
+ @engine.transaction
76
+ def function(tx):
77
+ pass
78
+
79
+ with self.assertRaises(TypeError):
80
+ function(tx=3)
81
+ with self.assertRaises(TypeError):
82
+ function(tx=None)
83
+
84
+ def test_transaction_injection_function(self, tx):
85
+ with engine.transaction() as original:
86
+
87
+ @engine.transaction
88
+ def function(tx):
89
+ assert tx != None
90
+ assert tx != original
91
+ self.assertIsInstance(tx, Transaction)
92
+
93
+ function()
94
+
95
+ @engine.transaction
96
+ def function(tx):
97
+ assert tx != None
98
+ assert tx == original
99
+ self.assertIsInstance(tx, Transaction)
100
+
101
+ function(original)
102
+
103
+ @engine.transaction
104
+ def function(tx=None):
105
+ assert tx != None
106
+ assert tx != original
107
+ self.assertIsInstance(tx, Transaction)
108
+
109
+ function()
110
+
111
+ @engine.transaction
112
+ def function(tx=None):
113
+ assert tx != None
114
+ assert tx == original
115
+ self.assertIsInstance(tx, Transaction)
116
+
117
+ function(original)
118
+
119
+ @engine.transaction
120
+ def function(tx=None):
121
+ assert tx != None
122
+ assert tx == original
123
+ self.assertIsInstance(tx, Transaction)
124
+
125
+ function(tx=original)
126
+
127
+ @engine.transaction
128
+ def function(tx, a, b):
129
+ assert tx != None
130
+ assert tx != original
131
+ self.assertIsInstance(tx, Transaction)
132
+
133
+ function(1, 2)
134
+
135
+ @engine.transaction
136
+ def function(tx, a, b):
137
+ assert tx != None
138
+ assert tx == original
139
+ self.assertIsInstance(tx, Transaction)
140
+
141
+ function(original, 1, 2)
142
+
143
+ @engine.transaction
144
+ def function(tx, a, b):
145
+ assert tx != None
146
+ assert tx == original
147
+ self.assertIsInstance(tx, Transaction)
148
+
149
+ function(tx=original, a=1, b=2)
150
+
151
+ @engine.transaction
152
+ def function(tx, src, b):
153
+ assert tx != None
154
+ assert tx != src
155
+ self.assertIsInstance(tx, Transaction)
156
+
157
+ function(src=original, b=2)
158
+
159
+ @engine.transaction
160
+ def function(a, tx, b):
161
+ assert tx != None
162
+ assert tx != original
163
+ self.assertIsInstance(tx, Transaction)
164
+
165
+ function(1, 2)
166
+
167
+ @engine.transaction
168
+ def function(a, tx, b):
169
+ assert tx != None
170
+ assert tx == original
171
+ self.assertIsInstance(tx, Transaction)
172
+
173
+ function(1, original, 2)
174
+
175
+ def test_transaction_injection_class(self, tx):
176
+ test_class = self
177
+ with engine.transaction() as original:
178
+
179
+ @engine.transaction
180
+ class TestClass:
181
+ @engine.transaction
182
+ def __init__(self, tx):
183
+ assert tx != None
184
+ assert tx != original
185
+ test_class.assertIsInstance(tx, Transaction)
186
+
187
+ def first_method(self, tx):
188
+ assert tx != None
189
+ assert tx != original
190
+ test_class.assertIsInstance(tx, Transaction)
191
+
192
+ def second_method(self, tx):
193
+ assert tx != None
194
+ assert tx == original
195
+ test_class.assertIsInstance(tx, Transaction)
196
+
197
+ tc = TestClass()
198
+
199
+ tc.first_method()
200
+ tc.second_method(original)
201
+ self.assertRaises(AssertionError, tc.second_method)
202
+
203
+
204
+ if __name__ == "__main__":
205
+ unittest.main()
@@ -0,0 +1,88 @@
1
+ import unittest
2
+ import sys
3
+ import os
4
+ from .common import CommonPostgresTest, engine, test_db
5
+
6
+
7
+ @engine.transaction
8
+ @engine.transaction
9
+ class TestFeatures(CommonPostgresTest):
10
+
11
+ @classmethod
12
+ def create_test_tables(cls, tx):
13
+ """Clean up any existing tables for general usage tests."""
14
+ # Ensure clean state by dropping tables if they exist
15
+ tx.table("names").drop()
16
+ tx.table("addresses").drop()
17
+
18
+ def test_foreign_key(self, tx):
19
+ first = tx.table("names")
20
+ second = tx.table("addresses")
21
+ first.create(
22
+ {
23
+ "first_name": str,
24
+ "last_name": str,
25
+ }
26
+ )
27
+ second.create(
28
+ {
29
+ "address": str,
30
+ "address2": str,
31
+ "city": str,
32
+ "state": str,
33
+ "zipcode": str,
34
+ "country": str,
35
+ "name_id": int,
36
+ }
37
+ )
38
+ tx.commit()
39
+ second.create_foreign_key("name_id", "names", "sys_id")
40
+ first.insert(
41
+ {
42
+ "first_name": "John",
43
+ "last_name": "Doe",
44
+ }
45
+ )
46
+ tx.commit()
47
+ second.insert(
48
+ {
49
+ "address": "123 Main St",
50
+ "address2": "Apt 123",
51
+ "city": "New York",
52
+ "state": "NY",
53
+ "zipcode": "12345",
54
+ "country": "USA",
55
+ "name_id": 1001,
56
+ }
57
+ )
58
+ second.insert(
59
+ {
60
+ "address": "123 Main St",
61
+ "address2": "Apt 123",
62
+ "city": "New York",
63
+ "state": "NY",
64
+ "zipcode": "12345",
65
+ "country": "USA",
66
+ "name_id": 1001,
67
+ }
68
+ )
69
+ second.insert(
70
+ {
71
+ "address": "123 Main St",
72
+ "address2": "Apt 123",
73
+ "city": "New York",
74
+ "state": "NY",
75
+ "zipcode": "12345",
76
+ "country": "USA",
77
+ "name_id": 1001,
78
+ }
79
+ )
80
+ addresses = second.select(where={"name_id": 1001})
81
+ # for address in addresses:
82
+ # print(address)
83
+
84
+ first.drop()
85
+
86
+
87
+ if __name__ == "__main__":
88
+ unittest.main()
@@ -0,0 +1,8 @@
1
+ import velocity
2
+ import velocity.db
3
+ import velocity.misc
4
+ import velocity.db.servers.postgres
5
+
6
+ # import velocity.db.servers.mysql
7
+ import velocity.db.servers.sqlite
8
+ import velocity.db.servers.sqlserver
@@ -0,0 +1,19 @@
1
+ import unittest
2
+ from velocity.db.core.result import Result
3
+ from .common import CommonPostgresTest, engine, test_db
4
+
5
+
6
+ @engine.transaction
7
+ @engine.transaction
8
+ class TestResult(CommonPostgresTest):
9
+
10
+ @classmethod
11
+ def create_test_tables(cls, tx):
12
+ """No special tables needed for result tests."""
13
+ pass
14
+ def test_result_all(self, tx):
15
+ result = tx.execute("SELECT current_timestamp")
16
+
17
+
18
+ if __name__ == "__main__":
19
+ unittest.main()