singlestoredb 1.16.1__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.
Files changed (183) hide show
  1. singlestoredb/__init__.py +75 -0
  2. singlestoredb/ai/__init__.py +2 -0
  3. singlestoredb/ai/chat.py +139 -0
  4. singlestoredb/ai/embeddings.py +128 -0
  5. singlestoredb/alchemy/__init__.py +90 -0
  6. singlestoredb/apps/__init__.py +3 -0
  7. singlestoredb/apps/_cloud_functions.py +90 -0
  8. singlestoredb/apps/_config.py +72 -0
  9. singlestoredb/apps/_connection_info.py +18 -0
  10. singlestoredb/apps/_dashboards.py +47 -0
  11. singlestoredb/apps/_process.py +32 -0
  12. singlestoredb/apps/_python_udfs.py +100 -0
  13. singlestoredb/apps/_stdout_supress.py +30 -0
  14. singlestoredb/apps/_uvicorn_util.py +36 -0
  15. singlestoredb/auth.py +245 -0
  16. singlestoredb/config.py +484 -0
  17. singlestoredb/connection.py +1487 -0
  18. singlestoredb/converters.py +950 -0
  19. singlestoredb/docstring/__init__.py +33 -0
  20. singlestoredb/docstring/attrdoc.py +126 -0
  21. singlestoredb/docstring/common.py +230 -0
  22. singlestoredb/docstring/epydoc.py +267 -0
  23. singlestoredb/docstring/google.py +412 -0
  24. singlestoredb/docstring/numpydoc.py +562 -0
  25. singlestoredb/docstring/parser.py +100 -0
  26. singlestoredb/docstring/py.typed +1 -0
  27. singlestoredb/docstring/rest.py +256 -0
  28. singlestoredb/docstring/tests/__init__.py +1 -0
  29. singlestoredb/docstring/tests/_pydoctor.py +21 -0
  30. singlestoredb/docstring/tests/test_epydoc.py +729 -0
  31. singlestoredb/docstring/tests/test_google.py +1007 -0
  32. singlestoredb/docstring/tests/test_numpydoc.py +1100 -0
  33. singlestoredb/docstring/tests/test_parse_from_object.py +109 -0
  34. singlestoredb/docstring/tests/test_parser.py +248 -0
  35. singlestoredb/docstring/tests/test_rest.py +547 -0
  36. singlestoredb/docstring/tests/test_util.py +70 -0
  37. singlestoredb/docstring/util.py +141 -0
  38. singlestoredb/exceptions.py +120 -0
  39. singlestoredb/functions/__init__.py +16 -0
  40. singlestoredb/functions/decorator.py +201 -0
  41. singlestoredb/functions/dtypes.py +1793 -0
  42. singlestoredb/functions/ext/__init__.py +1 -0
  43. singlestoredb/functions/ext/arrow.py +375 -0
  44. singlestoredb/functions/ext/asgi.py +2133 -0
  45. singlestoredb/functions/ext/json.py +420 -0
  46. singlestoredb/functions/ext/mmap.py +413 -0
  47. singlestoredb/functions/ext/rowdat_1.py +724 -0
  48. singlestoredb/functions/ext/timer.py +89 -0
  49. singlestoredb/functions/ext/utils.py +218 -0
  50. singlestoredb/functions/signature.py +1578 -0
  51. singlestoredb/functions/typing/__init__.py +41 -0
  52. singlestoredb/functions/typing/numpy.py +20 -0
  53. singlestoredb/functions/typing/pandas.py +2 -0
  54. singlestoredb/functions/typing/polars.py +2 -0
  55. singlestoredb/functions/typing/pyarrow.py +2 -0
  56. singlestoredb/functions/utils.py +421 -0
  57. singlestoredb/fusion/__init__.py +11 -0
  58. singlestoredb/fusion/graphql.py +213 -0
  59. singlestoredb/fusion/handler.py +916 -0
  60. singlestoredb/fusion/handlers/__init__.py +0 -0
  61. singlestoredb/fusion/handlers/export.py +525 -0
  62. singlestoredb/fusion/handlers/files.py +690 -0
  63. singlestoredb/fusion/handlers/job.py +660 -0
  64. singlestoredb/fusion/handlers/models.py +250 -0
  65. singlestoredb/fusion/handlers/stage.py +502 -0
  66. singlestoredb/fusion/handlers/utils.py +324 -0
  67. singlestoredb/fusion/handlers/workspace.py +956 -0
  68. singlestoredb/fusion/registry.py +249 -0
  69. singlestoredb/fusion/result.py +399 -0
  70. singlestoredb/http/__init__.py +27 -0
  71. singlestoredb/http/connection.py +1267 -0
  72. singlestoredb/magics/__init__.py +34 -0
  73. singlestoredb/magics/run_personal.py +137 -0
  74. singlestoredb/magics/run_shared.py +134 -0
  75. singlestoredb/management/__init__.py +9 -0
  76. singlestoredb/management/billing_usage.py +148 -0
  77. singlestoredb/management/cluster.py +462 -0
  78. singlestoredb/management/export.py +295 -0
  79. singlestoredb/management/files.py +1102 -0
  80. singlestoredb/management/inference_api.py +105 -0
  81. singlestoredb/management/job.py +887 -0
  82. singlestoredb/management/manager.py +373 -0
  83. singlestoredb/management/organization.py +226 -0
  84. singlestoredb/management/region.py +169 -0
  85. singlestoredb/management/utils.py +423 -0
  86. singlestoredb/management/workspace.py +1927 -0
  87. singlestoredb/mysql/__init__.py +177 -0
  88. singlestoredb/mysql/_auth.py +298 -0
  89. singlestoredb/mysql/charset.py +214 -0
  90. singlestoredb/mysql/connection.py +2032 -0
  91. singlestoredb/mysql/constants/CLIENT.py +38 -0
  92. singlestoredb/mysql/constants/COMMAND.py +32 -0
  93. singlestoredb/mysql/constants/CR.py +78 -0
  94. singlestoredb/mysql/constants/ER.py +474 -0
  95. singlestoredb/mysql/constants/EXTENDED_TYPE.py +3 -0
  96. singlestoredb/mysql/constants/FIELD_TYPE.py +48 -0
  97. singlestoredb/mysql/constants/FLAG.py +15 -0
  98. singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
  99. singlestoredb/mysql/constants/VECTOR_TYPE.py +6 -0
  100. singlestoredb/mysql/constants/__init__.py +0 -0
  101. singlestoredb/mysql/converters.py +271 -0
  102. singlestoredb/mysql/cursors.py +896 -0
  103. singlestoredb/mysql/err.py +92 -0
  104. singlestoredb/mysql/optionfile.py +20 -0
  105. singlestoredb/mysql/protocol.py +450 -0
  106. singlestoredb/mysql/tests/__init__.py +19 -0
  107. singlestoredb/mysql/tests/base.py +126 -0
  108. singlestoredb/mysql/tests/conftest.py +37 -0
  109. singlestoredb/mysql/tests/test_DictCursor.py +132 -0
  110. singlestoredb/mysql/tests/test_SSCursor.py +141 -0
  111. singlestoredb/mysql/tests/test_basic.py +452 -0
  112. singlestoredb/mysql/tests/test_connection.py +851 -0
  113. singlestoredb/mysql/tests/test_converters.py +58 -0
  114. singlestoredb/mysql/tests/test_cursor.py +141 -0
  115. singlestoredb/mysql/tests/test_err.py +16 -0
  116. singlestoredb/mysql/tests/test_issues.py +514 -0
  117. singlestoredb/mysql/tests/test_load_local.py +75 -0
  118. singlestoredb/mysql/tests/test_nextset.py +88 -0
  119. singlestoredb/mysql/tests/test_optionfile.py +27 -0
  120. singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
  121. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  122. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
  123. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
  124. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
  125. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
  126. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
  127. singlestoredb/mysql/times.py +23 -0
  128. singlestoredb/notebook/__init__.py +16 -0
  129. singlestoredb/notebook/_objects.py +213 -0
  130. singlestoredb/notebook/_portal.py +352 -0
  131. singlestoredb/py.typed +0 -0
  132. singlestoredb/pytest.py +352 -0
  133. singlestoredb/server/__init__.py +0 -0
  134. singlestoredb/server/docker.py +452 -0
  135. singlestoredb/server/free_tier.py +267 -0
  136. singlestoredb/tests/__init__.py +0 -0
  137. singlestoredb/tests/alltypes.sql +307 -0
  138. singlestoredb/tests/alltypes_no_nulls.sql +208 -0
  139. singlestoredb/tests/empty.sql +0 -0
  140. singlestoredb/tests/ext_funcs/__init__.py +702 -0
  141. singlestoredb/tests/local_infile.csv +3 -0
  142. singlestoredb/tests/test.ipynb +18 -0
  143. singlestoredb/tests/test.sql +680 -0
  144. singlestoredb/tests/test2.ipynb +18 -0
  145. singlestoredb/tests/test2.sql +1 -0
  146. singlestoredb/tests/test_basics.py +1332 -0
  147. singlestoredb/tests/test_config.py +318 -0
  148. singlestoredb/tests/test_connection.py +3103 -0
  149. singlestoredb/tests/test_dbapi.py +27 -0
  150. singlestoredb/tests/test_exceptions.py +45 -0
  151. singlestoredb/tests/test_ext_func.py +1472 -0
  152. singlestoredb/tests/test_ext_func_data.py +1101 -0
  153. singlestoredb/tests/test_fusion.py +1527 -0
  154. singlestoredb/tests/test_http.py +288 -0
  155. singlestoredb/tests/test_management.py +1599 -0
  156. singlestoredb/tests/test_plugin.py +33 -0
  157. singlestoredb/tests/test_results.py +171 -0
  158. singlestoredb/tests/test_types.py +132 -0
  159. singlestoredb/tests/test_udf.py +737 -0
  160. singlestoredb/tests/test_udf_returns.py +459 -0
  161. singlestoredb/tests/test_vectorstore.py +51 -0
  162. singlestoredb/tests/test_xdict.py +333 -0
  163. singlestoredb/tests/utils.py +141 -0
  164. singlestoredb/types.py +373 -0
  165. singlestoredb/utils/__init__.py +0 -0
  166. singlestoredb/utils/config.py +950 -0
  167. singlestoredb/utils/convert_rows.py +69 -0
  168. singlestoredb/utils/debug.py +13 -0
  169. singlestoredb/utils/dtypes.py +205 -0
  170. singlestoredb/utils/events.py +65 -0
  171. singlestoredb/utils/mogrify.py +151 -0
  172. singlestoredb/utils/results.py +585 -0
  173. singlestoredb/utils/xdict.py +425 -0
  174. singlestoredb/vectorstore.py +192 -0
  175. singlestoredb/warnings.py +5 -0
  176. singlestoredb-1.16.1.dist-info/METADATA +165 -0
  177. singlestoredb-1.16.1.dist-info/RECORD +183 -0
  178. singlestoredb-1.16.1.dist-info/WHEEL +5 -0
  179. singlestoredb-1.16.1.dist-info/entry_points.txt +2 -0
  180. singlestoredb-1.16.1.dist-info/licenses/LICENSE +201 -0
  181. singlestoredb-1.16.1.dist-info/top_level.txt +3 -0
  182. sqlx/__init__.py +4 -0
  183. sqlx/magic.py +113 -0
@@ -0,0 +1,37 @@
1
+ import platform
2
+
3
+ import singlestoredb.mysql as sv
4
+ from singlestoredb.connection import build_params
5
+
6
+
7
+ DBNAME_BASE = 'singlestoredb__test_%s_%s_%s_%s_' % \
8
+ (
9
+ *platform.python_version_tuple()[:2],
10
+ platform.system(), platform.machine(),
11
+ )
12
+
13
+
14
+ def pytest_sessionstart() -> None:
15
+ params = build_params()
16
+ conn = sv.connect( # type: ignore
17
+ host=params['host'], user=params['user'],
18
+ passwd=params['password'], port=params['port'],
19
+ buffered=params['buffered'],
20
+ )
21
+ cur = conn.cursor()
22
+ cur.execute(f'CREATE DATABASE IF NOT EXISTS {DBNAME_BASE}1')
23
+ cur.execute(f'CREATE DATABASE IF NOT EXISTS {DBNAME_BASE}2')
24
+ conn.close()
25
+
26
+
27
+ def pytest_sessionfinish() -> None:
28
+ params = build_params()
29
+ conn = sv.connect( # type: ignore
30
+ host=params['host'], user=params['user'],
31
+ passwd=params['password'], port=params['port'],
32
+ buffered=params['buffered'],
33
+ )
34
+ cur = conn.cursor()
35
+ cur.execute(f'DROP DATABASE {DBNAME_BASE}1')
36
+ cur.execute(f'DROP DATABASE {DBNAME_BASE}2')
37
+ conn.close()
@@ -0,0 +1,132 @@
1
+ # type: ignore
2
+ import datetime
3
+ import warnings
4
+
5
+ import pytest
6
+
7
+ import singlestoredb.mysql.cursors as cursors
8
+ from singlestoredb.mysql.tests import base
9
+
10
+
11
+ class TestDictCursor(base.PyMySQLTestCase):
12
+
13
+ bob = {'name': 'bob', 'age': 21, 'DOB': datetime.datetime(1990, 2, 6, 23, 4, 56)}
14
+ jim = {'name': 'jim', 'age': 56, 'DOB': datetime.datetime(1955, 5, 9, 13, 12, 45)}
15
+ fred = {'name': 'fred', 'age': 100, 'DOB': datetime.datetime(1911, 9, 12, 1, 1, 1)}
16
+
17
+ cursor_type = cursors.DictCursor
18
+
19
+ def setUp(self):
20
+ super(TestDictCursor, self).setUp()
21
+ self.conn = conn = self.connect(cursorclass=self.cursor_type)
22
+ c = conn.cursor()
23
+
24
+ # create a table ane some data to query
25
+ with warnings.catch_warnings():
26
+ warnings.filterwarnings('ignore')
27
+ c.execute('drop table if exists dictcursor')
28
+ # include in filterwarnings since for unbuffered dict cursor warning
29
+ # for lack of table will only be propagated at start of next execute() call
30
+ c.execute(
31
+ """CREATE TABLE dictcursor (name char(20), age int , DOB datetime)""",
32
+ )
33
+ data = [
34
+ ('bob', 21, '1990-02-06 23:04:56'),
35
+ ('jim', 56, '1955-05-09 13:12:45'),
36
+ ('fred', 100, '1911-09-12 01:01:01'),
37
+ ]
38
+ c.executemany('insert into dictcursor values (%s,%s,%s)', data)
39
+
40
+ def tearDown(self):
41
+ c = self.conn.cursor()
42
+ c.execute('drop table dictcursor')
43
+ super(TestDictCursor, self).tearDown()
44
+
45
+ def _ensure_cursor_expired(self, cursor):
46
+ pass
47
+
48
+ def test_DictCursor(self):
49
+ bob, jim, fred = self.bob.copy(), self.jim.copy(), self.fred.copy()
50
+ # all assert test compare to the structure as would come out from MySQLdb
51
+ conn = self.conn
52
+ c = conn.cursor()
53
+
54
+ # try an update which should return no rows
55
+ c.execute("update dictcursor set age=20 where name='bob'")
56
+ bob['age'] = 20
57
+ # pull back the single row dict for bob and check
58
+ c.execute("SELECT * from dictcursor where name='bob'")
59
+ r = c.fetchone()
60
+ self.assertEqual(bob, r, 'fetchone via DictCursor failed')
61
+ self._ensure_cursor_expired(c)
62
+
63
+ # same again, but via fetchall => tuple)
64
+ c.execute("SELECT * from dictcursor where name='bob'")
65
+ r = c.fetchall()
66
+ self.assertEqual(
67
+ [bob], r, 'fetch a 1 row result via fetchall failed via DictCursor',
68
+ )
69
+ # same test again but iterate over the
70
+ c.execute("SELECT * from dictcursor where name='bob'")
71
+ for r in c:
72
+ self.assertEqual(
73
+ bob, r, 'fetch a 1 row result via iteration failed via DictCursor',
74
+ )
75
+ # get all 3 row via fetchall
76
+ c.execute('SELECT * from dictcursor ORDER BY name')
77
+ r = c.fetchall()
78
+ self.assertEqual([bob, fred, jim], r, 'fetchall failed via DictCursor')
79
+ # same test again but do a list comprehension
80
+ c.execute('SELECT * from dictcursor ORDER BY name')
81
+ r = list(c)
82
+ self.assertEqual([bob, fred, jim], r, 'DictCursor should be iterable')
83
+ # get all 2 row via fetchmany
84
+ c.execute('SELECT * from dictcursor ORDER BY name')
85
+ r = c.fetchmany(2)
86
+ self.assertEqual([bob, fred], r, 'fetchmany failed via DictCursor')
87
+ self._ensure_cursor_expired(c)
88
+
89
+ @pytest.mark.skip('Custom cursors are only available when creating a connection')
90
+ def test_custom_dict(self):
91
+ class MyDict(dict):
92
+ pass
93
+
94
+ class MyDictCursor(self.cursor_type):
95
+ dict_type = MyDict
96
+
97
+ keys = ['name', 'age', 'DOB']
98
+ bob = MyDict([(k, self.bob[k]) for k in keys])
99
+ jim = MyDict([(k, self.jim[k]) for k in keys])
100
+ fred = MyDict([(k, self.fred[k]) for k in keys])
101
+
102
+ cur = self.conn.cursor(MyDictCursor)
103
+ cur.execute("SELECT * FROM dictcursor WHERE name='bob'")
104
+ r = cur.fetchone()
105
+ self.assertEqual(bob, r, 'fetchone() returns MyDictCursor')
106
+ self._ensure_cursor_expired(cur)
107
+
108
+ cur.execute('SELECT * FROM dictcursor ORDER BY name')
109
+ r = cur.fetchall()
110
+ self.assertEqual([bob, fred, jim], r, 'fetchall failed via MyDictCursor')
111
+
112
+ cur.execute('SELECT * FROM dictcursor ORDER BY name')
113
+ r = list(cur)
114
+ self.assertEqual([bob, fred, jim], r, 'list failed via MyDictCursor')
115
+
116
+ cur.execute('SELECT * FROM dictcursor ORDER BY name')
117
+ r = cur.fetchmany(2)
118
+ self.assertEqual([bob, fred], r, 'list failed via MyDictCursor')
119
+ self._ensure_cursor_expired(cur)
120
+
121
+
122
+ class TestSSDictCursor(TestDictCursor):
123
+ cursor_type = cursors.SSDictCursor
124
+
125
+ def _ensure_cursor_expired(self, cursor):
126
+ list(cursor.fetchall_unbuffered())
127
+
128
+
129
+ if __name__ == '__main__':
130
+ import unittest
131
+
132
+ unittest.main()
@@ -0,0 +1,141 @@
1
+ # type: ignore
2
+ import sys
3
+
4
+ try:
5
+ import singlestoredb.mysql as sv
6
+ from singlestoredb.mysql.tests import base
7
+ import singlestoredb.mysql.cursors as cursors
8
+ from singlestoredb.mysql.constants import CLIENT
9
+ except Exception:
10
+ # For local testing from top-level directory, without installing
11
+ sys.path.append('../../..')
12
+ import singlestoredb.mysql as sv # noqa: F401
13
+ from singlestoredb.mysql.tests import base
14
+ import singlestoredb.mysql.cursors as cursors
15
+ from singlestoredb.mysql.constants import CLIENT
16
+
17
+
18
+ class TestSSCursor(base.PyMySQLTestCase):
19
+
20
+ def test_SSCursor(self):
21
+ # affected_rows = 18446744073709551615
22
+ affected_rows = -1
23
+
24
+ conn = self.connect(
25
+ client_flag=CLIENT.MULTI_STATEMENTS,
26
+ cursorclass=cursors.SSCursor,
27
+ )
28
+ data = [
29
+ ('America', '', 'America/Jamaica'),
30
+ ('America', '', 'America/Los_Angeles'),
31
+ ('America', '', 'America/Lima'),
32
+ ('America', '', 'America/New_York'),
33
+ ('America', '', 'America/Menominee'),
34
+ ('America', '', 'America/Havana'),
35
+ ('America', '', 'America/El_Salvador'),
36
+ ('America', '', 'America/Costa_Rica'),
37
+ ('America', '', 'America/Denver'),
38
+ ('America', '', 'America/Detroit'),
39
+ ]
40
+
41
+ cursor = conn.cursor()
42
+
43
+ cursor.execute('DROP TABLE IF EXISTS tz_data')
44
+
45
+ # Create table
46
+ cursor.execute(
47
+ 'CREATE TABLE tz_data ('
48
+ 'region VARCHAR(64),'
49
+ 'zone VARCHAR(64),'
50
+ 'name VARCHAR(64))',
51
+ )
52
+
53
+ conn.begin()
54
+ # Test INSERT
55
+ for i in data:
56
+ cursor.execute('INSERT INTO tz_data VALUES (%s, %s, %s)', i)
57
+ self.assertEqual(conn.affected_rows(), 1, 'affected_rows does not match')
58
+ conn.commit()
59
+
60
+ # Test fetchone()
61
+ iter = 0
62
+ cursor.execute('SELECT * FROM tz_data')
63
+ while True:
64
+ row = cursor.fetchone()
65
+ if row is None:
66
+ break
67
+ iter += 1
68
+
69
+ # Test cursor.rowcount
70
+ self.assertEqual(
71
+ cursor.rowcount,
72
+ affected_rows,
73
+ 'cursor.rowcount != %s' % (str(affected_rows)),
74
+ )
75
+
76
+ # Test cursor.rownumber
77
+ self.assertEqual(
78
+ cursor.rownumber, iter, 'cursor.rowcount != %s' % (str(iter)),
79
+ )
80
+
81
+ # Test row came out the same as it went in
82
+ self.assertEqual((row in data), True, 'Row not found in source data')
83
+
84
+ # Test fetchall
85
+ cursor.execute('SELECT * FROM tz_data')
86
+ self.assertEqual(
87
+ len(cursor.fetchall()),
88
+ len(data),
89
+ 'fetchall failed. Number of rows does not match',
90
+ )
91
+
92
+ # Test fetchmany
93
+ cursor.execute('SELECT * FROM tz_data')
94
+ self.assertEqual(
95
+ len(cursor.fetchmany(2)),
96
+ 2,
97
+ 'fetchmany failed. Number of rows does not match',
98
+ )
99
+
100
+ # So MySQLdb won't throw "Commands out of sync"
101
+ while True:
102
+ res = cursor.fetchone()
103
+ if res is None:
104
+ break
105
+
106
+ # Test update, affected_rows()
107
+ cursor.execute('UPDATE tz_data SET zone = %s', ['Foo'])
108
+ conn.commit()
109
+ self.assertEqual(
110
+ cursor.rowcount,
111
+ len(data),
112
+ 'Update failed. affected_rows != %s' % (str(len(data))),
113
+ )
114
+
115
+ # Test executemany
116
+ cursor.executemany('INSERT INTO tz_data VALUES (%s, %s, %s)', data)
117
+ self.assertEqual(
118
+ cursor.rowcount,
119
+ len(data),
120
+ 'executemany failed. cursor.rowcount != %s' % (str(len(data))),
121
+ )
122
+
123
+ # Test multiple datasets
124
+ cursor.execute('SELECT 1; SELECT 2; SELECT 3')
125
+ self.assertListEqual(list(cursor), [(1,)])
126
+ self.assertTrue(cursor.nextset())
127
+ self.assertListEqual(list(cursor), [(2,)])
128
+ self.assertTrue(cursor.nextset())
129
+ self.assertListEqual(list(cursor), [(3,)])
130
+ self.assertFalse(cursor.nextset())
131
+
132
+ cursor.execute('DROP TABLE IF EXISTS tz_data')
133
+ cursor.close()
134
+
135
+
136
+ __all__ = ['TestSSCursor']
137
+
138
+ if __name__ == '__main__':
139
+ import unittest
140
+
141
+ unittest.main()