singlestoredb 0.3.3__py3-none-any.whl → 1.0.3__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 singlestoredb might be problematic. Click here for more details.

Files changed (121) hide show
  1. singlestoredb/__init__.py +33 -2
  2. singlestoredb/alchemy/__init__.py +90 -0
  3. singlestoredb/auth.py +6 -4
  4. singlestoredb/config.py +116 -16
  5. singlestoredb/connection.py +489 -523
  6. singlestoredb/converters.py +275 -26
  7. singlestoredb/exceptions.py +30 -4
  8. singlestoredb/functions/__init__.py +1 -0
  9. singlestoredb/functions/decorator.py +142 -0
  10. singlestoredb/functions/dtypes.py +1639 -0
  11. singlestoredb/functions/ext/__init__.py +2 -0
  12. singlestoredb/functions/ext/arrow.py +375 -0
  13. singlestoredb/functions/ext/asgi.py +661 -0
  14. singlestoredb/functions/ext/json.py +427 -0
  15. singlestoredb/functions/ext/mmap.py +306 -0
  16. singlestoredb/functions/ext/rowdat_1.py +744 -0
  17. singlestoredb/functions/signature.py +673 -0
  18. singlestoredb/fusion/__init__.py +11 -0
  19. singlestoredb/fusion/graphql.py +213 -0
  20. singlestoredb/fusion/handler.py +621 -0
  21. singlestoredb/fusion/handlers/__init__.py +0 -0
  22. singlestoredb/fusion/handlers/stage.py +257 -0
  23. singlestoredb/fusion/handlers/utils.py +162 -0
  24. singlestoredb/fusion/handlers/workspace.py +412 -0
  25. singlestoredb/fusion/registry.py +164 -0
  26. singlestoredb/fusion/result.py +399 -0
  27. singlestoredb/http/__init__.py +27 -0
  28. singlestoredb/http/connection.py +1192 -0
  29. singlestoredb/management/__init__.py +3 -2
  30. singlestoredb/management/billing_usage.py +148 -0
  31. singlestoredb/management/cluster.py +19 -14
  32. singlestoredb/management/manager.py +100 -40
  33. singlestoredb/management/organization.py +188 -0
  34. singlestoredb/management/region.py +6 -8
  35. singlestoredb/management/utils.py +253 -4
  36. singlestoredb/management/workspace.py +1153 -35
  37. singlestoredb/mysql/__init__.py +177 -0
  38. singlestoredb/mysql/_auth.py +298 -0
  39. singlestoredb/mysql/charset.py +214 -0
  40. singlestoredb/mysql/connection.py +1814 -0
  41. singlestoredb/mysql/constants/CLIENT.py +38 -0
  42. singlestoredb/mysql/constants/COMMAND.py +32 -0
  43. singlestoredb/mysql/constants/CR.py +78 -0
  44. singlestoredb/mysql/constants/ER.py +474 -0
  45. singlestoredb/mysql/constants/FIELD_TYPE.py +32 -0
  46. singlestoredb/mysql/constants/FLAG.py +15 -0
  47. singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
  48. singlestoredb/mysql/constants/__init__.py +0 -0
  49. singlestoredb/mysql/converters.py +271 -0
  50. singlestoredb/mysql/cursors.py +713 -0
  51. singlestoredb/mysql/err.py +92 -0
  52. singlestoredb/mysql/optionfile.py +20 -0
  53. singlestoredb/mysql/protocol.py +388 -0
  54. singlestoredb/mysql/tests/__init__.py +19 -0
  55. singlestoredb/mysql/tests/base.py +126 -0
  56. singlestoredb/mysql/tests/conftest.py +37 -0
  57. singlestoredb/mysql/tests/test_DictCursor.py +132 -0
  58. singlestoredb/mysql/tests/test_SSCursor.py +141 -0
  59. singlestoredb/mysql/tests/test_basic.py +452 -0
  60. singlestoredb/mysql/tests/test_connection.py +851 -0
  61. singlestoredb/mysql/tests/test_converters.py +58 -0
  62. singlestoredb/mysql/tests/test_cursor.py +141 -0
  63. singlestoredb/mysql/tests/test_err.py +16 -0
  64. singlestoredb/mysql/tests/test_issues.py +514 -0
  65. singlestoredb/mysql/tests/test_load_local.py +75 -0
  66. singlestoredb/mysql/tests/test_nextset.py +88 -0
  67. singlestoredb/mysql/tests/test_optionfile.py +27 -0
  68. singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
  69. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  70. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
  71. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
  72. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
  73. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
  74. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
  75. singlestoredb/mysql/times.py +23 -0
  76. singlestoredb/pytest.py +283 -0
  77. singlestoredb/tests/empty.sql +0 -0
  78. singlestoredb/tests/ext_funcs/__init__.py +385 -0
  79. singlestoredb/tests/test.sql +210 -0
  80. singlestoredb/tests/test2.sql +1 -0
  81. singlestoredb/tests/test_basics.py +482 -117
  82. singlestoredb/tests/test_config.py +13 -15
  83. singlestoredb/tests/test_connection.py +241 -289
  84. singlestoredb/tests/test_dbapi.py +27 -0
  85. singlestoredb/tests/test_exceptions.py +0 -2
  86. singlestoredb/tests/test_ext_func.py +1193 -0
  87. singlestoredb/tests/test_ext_func_data.py +1101 -0
  88. singlestoredb/tests/test_fusion.py +465 -0
  89. singlestoredb/tests/test_http.py +32 -28
  90. singlestoredb/tests/test_management.py +588 -10
  91. singlestoredb/tests/test_plugin.py +33 -0
  92. singlestoredb/tests/test_results.py +11 -14
  93. singlestoredb/tests/test_types.py +0 -2
  94. singlestoredb/tests/test_udf.py +687 -0
  95. singlestoredb/tests/test_xdict.py +0 -2
  96. singlestoredb/tests/utils.py +3 -4
  97. singlestoredb/types.py +4 -5
  98. singlestoredb/utils/config.py +71 -12
  99. singlestoredb/utils/convert_rows.py +0 -2
  100. singlestoredb/utils/debug.py +13 -0
  101. singlestoredb/utils/mogrify.py +151 -0
  102. singlestoredb/utils/results.py +4 -3
  103. singlestoredb/utils/xdict.py +12 -12
  104. singlestoredb-1.0.3.dist-info/METADATA +139 -0
  105. singlestoredb-1.0.3.dist-info/RECORD +112 -0
  106. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/WHEEL +1 -1
  107. singlestoredb-1.0.3.dist-info/entry_points.txt +2 -0
  108. singlestoredb/drivers/__init__.py +0 -46
  109. singlestoredb/drivers/base.py +0 -200
  110. singlestoredb/drivers/cymysql.py +0 -40
  111. singlestoredb/drivers/http.py +0 -49
  112. singlestoredb/drivers/mariadb.py +0 -42
  113. singlestoredb/drivers/mysqlconnector.py +0 -51
  114. singlestoredb/drivers/mysqldb.py +0 -62
  115. singlestoredb/drivers/pymysql.py +0 -39
  116. singlestoredb/drivers/pyodbc.py +0 -67
  117. singlestoredb/http.py +0 -794
  118. singlestoredb-0.3.3.dist-info/METADATA +0 -105
  119. singlestoredb-0.3.3.dist-info/RECORD +0 -46
  120. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/LICENSE +0 -0
  121. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1192 @@
1
+ #!/usr/bin/env python
2
+ """SingleStoreDB HTTP API interface."""
3
+ import datetime
4
+ import decimal
5
+ import functools
6
+ import json
7
+ import math
8
+ import os
9
+ import re
10
+ import time
11
+ from base64 import b64decode
12
+ from typing import Any
13
+ from typing import Callable
14
+ from typing import Dict
15
+ from typing import Iterable
16
+ from typing import List
17
+ from typing import Optional
18
+ from typing import Sequence
19
+ from typing import Tuple
20
+ from typing import Union
21
+ from urllib.parse import urljoin
22
+ from urllib.parse import urlparse
23
+
24
+ import requests
25
+
26
+ try:
27
+ import numpy as np
28
+ has_numpy = True
29
+ except ImportError:
30
+ has_numpy = False
31
+
32
+ try:
33
+ import pygeos
34
+ has_pygeos = True
35
+ except ImportError:
36
+ has_pygeos = False
37
+
38
+ try:
39
+ import shapely.geometry
40
+ import shapely.wkt
41
+ has_shapely = True
42
+ except ImportError:
43
+ has_shapely = False
44
+
45
+ from .. import connection
46
+ from .. import fusion
47
+ from .. import types
48
+ from ..config import get_option
49
+ from ..converters import converters
50
+ from ..exceptions import DatabaseError # noqa: F401
51
+ from ..exceptions import DataError
52
+ from ..exceptions import Error # noqa: F401
53
+ from ..exceptions import IntegrityError
54
+ from ..exceptions import InterfaceError
55
+ from ..exceptions import InternalError
56
+ from ..exceptions import NotSupportedError
57
+ from ..exceptions import OperationalError
58
+ from ..exceptions import ProgrammingError
59
+ from ..exceptions import Warning # noqa: F401
60
+ from ..utils.convert_rows import convert_rows
61
+ from ..utils.debug import log_query
62
+ from ..utils.mogrify import mogrify
63
+ from ..utils.results import Description
64
+ from ..utils.results import format_results
65
+ from ..utils.results import Result
66
+
67
+
68
+ # DB-API settings
69
+ apilevel = '2.0'
70
+ paramstyle = 'named'
71
+ threadsafety = 1
72
+
73
+
74
+ _interface_errors = set([
75
+ 0,
76
+ 2013, # CR_SERVER_LOST
77
+ 2006, # CR_SERVER_GONE_ERROR
78
+ 2012, # CR_HANDSHAKE_ERR
79
+ 2004, # CR_IPSOCK_ERROR
80
+ 2014, # CR_COMMANDS_OUT_OF_SYNC
81
+ ])
82
+ _data_errors = set([
83
+ 1406, # ER_DATA_TOO_LONG
84
+ 1441, # ER_DATETIME_FUNCTION_OVERFLOW
85
+ 1365, # ER_DIVISION_BY_ZERO
86
+ 1230, # ER_NO_DEFAULT
87
+ 1171, # ER_PRIMARY_CANT_HAVE_NULL
88
+ 1264, # ER_WARN_DATA_OUT_OF_RANGE
89
+ 1265, # ER_WARN_DATA_TRUNCATED
90
+ ])
91
+ _programming_errors = set([
92
+ 1065, # ER_EMPTY_QUERY
93
+ 1179, # ER_CANT_DO_THIS_DURING_AN_TRANSACTION
94
+ 1007, # ER_DB_CREATE_EXISTS
95
+ 1110, # ER_FIELD_SPECIFIED_TWICE
96
+ 1111, # ER_INVALID_GROUP_FUNC_USE
97
+ 1082, # ER_NO_SUCH_INDEX
98
+ 1741, # ER_NO_SUCH_KEY_VALUE
99
+ 1146, # ER_NO_SUCH_TABLE
100
+ 1449, # ER_NO_SUCH_USER
101
+ 1064, # ER_PARSE_ERROR
102
+ 1149, # ER_SYNTAX_ERROR
103
+ 1113, # ER_TABLE_MUST_HAVE_COLUMNS
104
+ 1112, # ER_UNSUPPORTED_EXTENSION
105
+ 1102, # ER_WRONG_DB_NAME
106
+ 1103, # ER_WRONG_TABLE_NAME
107
+ 1049, # ER_BAD_DB_ERROR
108
+ 1582, # ER_??? Wrong number of args
109
+ ])
110
+ _integrity_errors = set([
111
+ 1215, # ER_CANNOT_ADD_FOREIGN
112
+ 1062, # ER_DUP_ENTRY
113
+ 1169, # ER_DUP_UNIQUE
114
+ 1364, # ER_NO_DEFAULT_FOR_FIELD
115
+ 1216, # ER_NO_REFERENCED_ROW
116
+ 1452, # ER_NO_REFERENCED_ROW_2
117
+ 1217, # ER_ROW_IS_REFERENCED
118
+ 1451, # ER_ROW_IS_REFERENCED_2
119
+ 1460, # ER_XAER_OUTSIDE
120
+ 1401, # ER_XAER_RMERR
121
+ 1048, # ER_BAD_NULL_ERROR
122
+ 1264, # ER_DATA_OUT_OF_RANGE
123
+ 4025, # ER_CONSTRAINT_FAILED
124
+ 1826, # ER_DUP_CONSTRAINT_NAME
125
+ ])
126
+
127
+
128
+ def get_precision_scale(type_code: str) -> Tuple[Optional[int], Optional[int]]:
129
+ """Parse the precision and scale from a data type."""
130
+ if '(' not in type_code:
131
+ return (None, None)
132
+ m = re.search(r'\(\s*(\d+)\s*,\s*(\d+)\s*\)', type_code)
133
+ if m:
134
+ return int(m.group(1)), int(m.group(2))
135
+ m = re.search(r'\(\s*(\d+)\s*\)', type_code)
136
+ if m:
137
+ return (int(m.group(1)), None)
138
+ raise ValueError(f'Unrecognized type code: {type_code}')
139
+
140
+
141
+ def get_exc_type(code: int) -> type:
142
+ """Map error code to DB-API error type."""
143
+ if code in _interface_errors:
144
+ return InterfaceError
145
+ if code in _data_errors:
146
+ return DataError
147
+ if code in _programming_errors:
148
+ return ProgrammingError
149
+ if code in _integrity_errors:
150
+ return IntegrityError
151
+ if code >= 1000:
152
+ return OperationalError
153
+ return InternalError
154
+
155
+
156
+ def identity(x: Any) -> Any:
157
+ """Return input value."""
158
+ return x
159
+
160
+
161
+ def b64decode_converter(
162
+ converter: Callable[..., Any],
163
+ x: Optional[str],
164
+ encoding: str = 'utf-8',
165
+ ) -> Optional[bytes]:
166
+ """Decode value before applying converter."""
167
+ if x is None:
168
+ return None
169
+ if converter is None:
170
+ return b64decode(x)
171
+ return converter(b64decode(x))
172
+
173
+
174
+ def encode_timedelta(obj: datetime.timedelta) -> str:
175
+ """Encode timedelta as str."""
176
+ seconds = int(obj.seconds) % 60
177
+ minutes = int(obj.seconds // 60) % 60
178
+ hours = int(obj.seconds // 3600) % 24 + int(obj.days) * 24
179
+ if obj.microseconds:
180
+ fmt = '{0:02d}:{1:02d}:{2:02d}.{3:06d}'
181
+ else:
182
+ fmt = '{0:02d}:{1:02d}:{2:02d}'
183
+ return fmt.format(hours, minutes, seconds, obj.microseconds)
184
+
185
+
186
+ def encode_time(obj: datetime.time) -> str:
187
+ """Encode time as str."""
188
+ if obj.microsecond:
189
+ fmt = '{0.hour:02}:{0.minute:02}:{0.second:02}.{0.microsecond:06}'
190
+ else:
191
+ fmt = '{0.hour:02}:{0.minute:02}:{0.second:02}'
192
+ return fmt.format(obj)
193
+
194
+
195
+ def encode_datetime(obj: datetime.datetime) -> str:
196
+ """Encode datetime as str."""
197
+ if obj.microsecond:
198
+ fmt = '{0.year:04}-{0.month:02}-{0.day:02} ' \
199
+ '{0.hour:02}:{0.minute:02}:{0.second:02}.{0.microsecond:06}'
200
+ else:
201
+ fmt = '{0.year:04}-{0.month:02}-{0.day:02} ' \
202
+ '{0.hour:02}:{0.minute:02}:{0.second:02}'
203
+ return fmt.format(obj)
204
+
205
+
206
+ def encode_date(obj: datetime.date) -> str:
207
+ """Encode date as str."""
208
+ fmt = '{0.year:04}-{0.month:02}-{0.day:02}'
209
+ return fmt.format(obj)
210
+
211
+
212
+ def encode_struct_time(obj: time.struct_time) -> str:
213
+ """Encode time struct to str."""
214
+ return encode_datetime(datetime.datetime(*obj[:6]))
215
+
216
+
217
+ def encode_decimal(o: decimal.Decimal) -> str:
218
+ """Encode decimal to str."""
219
+ return format(o, 'f')
220
+
221
+
222
+ # Most argument encoding is done by the JSON encoder, but these
223
+ # are exceptions to the rule.
224
+ encoders = {
225
+ datetime.datetime: encode_datetime,
226
+ datetime.date: encode_date,
227
+ datetime.time: encode_time,
228
+ datetime.timedelta: encode_timedelta,
229
+ time.struct_time: encode_struct_time,
230
+ decimal.Decimal: encode_decimal,
231
+ }
232
+
233
+
234
+ if has_shapely:
235
+ encoders[shapely.geometry.Point] = shapely.wkt.dumps
236
+ encoders[shapely.geometry.Polygon] = shapely.wkt.dumps
237
+ encoders[shapely.geometry.LineString] = shapely.wkt.dumps
238
+
239
+ if has_numpy:
240
+
241
+ def encode_ndarray(obj: np.ndarray) -> bytes: # type: ignore
242
+ """Encode an ndarray as bytes."""
243
+ return obj.tobytes()
244
+
245
+ encoders[np.ndarray] = encode_ndarray
246
+
247
+ if has_pygeos:
248
+ encoders[pygeos.Geometry] = pygeos.io.to_wkt
249
+
250
+
251
+ def convert_special_type(
252
+ arg: Any,
253
+ nan_as_null: bool = False,
254
+ inf_as_null: bool = False,
255
+ ) -> Any:
256
+ """Convert special data type objects."""
257
+ dtype = type(arg)
258
+ if dtype is float or \
259
+ (
260
+ has_numpy and dtype in (
261
+ np.float16, np.float32, np.float64,
262
+ getattr(np, 'float128', np.float64),
263
+ )
264
+ ):
265
+ if nan_as_null and math.isnan(arg):
266
+ return None
267
+ if inf_as_null and math.isinf(arg):
268
+ return None
269
+ func = encoders.get(dtype, None)
270
+ if func is not None:
271
+ return func(arg) # type: ignore
272
+ return arg
273
+
274
+
275
+ def convert_special_params(
276
+ params: Optional[Union[Sequence[Any], Dict[str, Any]]] = None,
277
+ nan_as_null: bool = False,
278
+ inf_as_null: bool = False,
279
+ ) -> Optional[Union[Sequence[Any], Dict[str, Any]]]:
280
+ """Convert parameters of special data types."""
281
+ if params is None:
282
+ return params
283
+ converter = functools.partial(
284
+ convert_special_type,
285
+ nan_as_null=nan_as_null,
286
+ inf_as_null=inf_as_null,
287
+ )
288
+ if isinstance(params, Dict):
289
+ return {k: converter(v) for k, v in params.items()}
290
+ return tuple(map(converter, params))
291
+
292
+
293
+ class PyMyField(object):
294
+ """Field for PyMySQL compatibility."""
295
+
296
+ def __init__(self, name: str, flags: int, charset: int) -> None:
297
+ self.name = name
298
+ self.flags = flags
299
+ self.charsetnr = charset
300
+
301
+
302
+ class PyMyResult(object):
303
+ """Result for PyMySQL compatibility."""
304
+
305
+ def __init__(self) -> None:
306
+ self.fields: List[PyMyField] = []
307
+ self.unbuffered_active = False
308
+
309
+ def append(self, item: PyMyField) -> None:
310
+ self.fields.append(item)
311
+
312
+
313
+ class Cursor(connection.Cursor):
314
+ """
315
+ SingleStoreDB HTTP database cursor.
316
+
317
+ Cursor objects should not be created directly. They should come from
318
+ the `cursor` method on the `Connection` object.
319
+
320
+ Parameters
321
+ ----------
322
+ connection : Connection
323
+ The HTTP Connection object the cursor belongs to
324
+
325
+ """
326
+
327
+ def __init__(self, conn: 'Connection'):
328
+ connection.Cursor.__init__(self, conn)
329
+ self._connection: Optional[Connection] = conn
330
+ self._results: List[List[Tuple[Any, ...]]] = [[]]
331
+ self._results_type: str = self._connection._results_type \
332
+ if self._connection is not None else 'tuples'
333
+ self._row_idx: int = -1
334
+ self._result_idx: int = -1
335
+ self._descriptions: List[List[Description]] = []
336
+ self.arraysize: int = get_option('results.arraysize')
337
+ self.rowcount: int = 0
338
+ self.lastrowid: Optional[int] = None
339
+ self._pymy_results: List[PyMyResult] = []
340
+ self._expect_results: bool = False
341
+
342
+ @property
343
+ def _result(self) -> Optional[PyMyResult]:
344
+ """Return Result object for PyMySQL compatibility."""
345
+ if self._result_idx < 0:
346
+ return None
347
+ return self._pymy_results[self._result_idx]
348
+
349
+ @property
350
+ def description(self) -> Optional[List[Description]]:
351
+ """Return description for current result set."""
352
+ if not self._descriptions:
353
+ return None
354
+ if self._result_idx >= 0 and self._result_idx < len(self._descriptions):
355
+ return self._descriptions[self._result_idx]
356
+ return None
357
+
358
+ def _post(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
359
+ """
360
+ Invoke a POST request on the HTTP connection.
361
+
362
+ Parameters
363
+ ----------
364
+ path : str
365
+ The path of the resource
366
+ *args : positional parameters, optional
367
+ Extra parameters to the POST request
368
+ **kwargs : keyword parameters, optional
369
+ Extra keyword parameters to the POST request
370
+
371
+ Returns
372
+ -------
373
+ requests.Response
374
+
375
+ """
376
+ if self._connection is None:
377
+ raise ProgrammingError(errno=2048, msg='Connection is closed.')
378
+ return self._connection._post(path, *args, **kwargs)
379
+
380
+ def callproc(
381
+ self, name: str,
382
+ params: Optional[Sequence[Any]] = None,
383
+ ) -> None:
384
+ """
385
+ Call a stored procedure.
386
+
387
+ Parameters
388
+ ----------
389
+ name : str
390
+ Name of the stored procedure
391
+ params : sequence, optional
392
+ Parameters to the stored procedure
393
+
394
+ """
395
+ if self._connection is None:
396
+ raise ProgrammingError(errno=2048, msg='Connection is closed.')
397
+
398
+ name = connection._name_check(name)
399
+
400
+ if not params:
401
+ self._execute(f'CALL {name}();', is_callproc=True)
402
+ else:
403
+ keys = ', '.join(['%s' for i in range(len(params))])
404
+ self._execute(f'CALL {name}({keys});', params, is_callproc=True)
405
+
406
+ def close(self) -> None:
407
+ """Close the cursor."""
408
+ self._connection = None
409
+
410
+ def execute(
411
+ self, query: str,
412
+ args: Optional[Union[Sequence[Any], Dict[str, Any]]] = None,
413
+ ) -> int:
414
+ """
415
+ Execute a SQL statement.
416
+
417
+ Parameters
418
+ ----------
419
+ query : str
420
+ The SQL statement to execute
421
+ args : iterable or dict, optional
422
+ Parameters to substitute into the SQL code
423
+
424
+ """
425
+ return self._execute(query, args)
426
+
427
+ def _validate_param_subs(
428
+ self, query: str,
429
+ args: Optional[Union[Sequence[Any], Dict[str, Any]]] = None,
430
+ ) -> None:
431
+ """Make sure the parameter substitions are valid."""
432
+ if args:
433
+ if isinstance(args, Sequence):
434
+ query = query % tuple(args)
435
+ else:
436
+ query = query % args
437
+
438
+ def _execute_fusion_query(
439
+ self,
440
+ oper: Union[str, bytes],
441
+ params: Optional[Union[Sequence[Any], Dict[str, Any]]] = None,
442
+ handler: Any = None,
443
+ ) -> int:
444
+ oper = mogrify(oper, params)
445
+
446
+ if isinstance(oper, bytes):
447
+ oper = oper.decode('utf-8')
448
+
449
+ log_query(oper, None)
450
+
451
+ results_type = self._results_type
452
+ self._results_type = 'tuples'
453
+ try:
454
+ mgmt_res = fusion.execute(
455
+ self._connection, # type: ignore
456
+ oper,
457
+ handler=handler,
458
+ )
459
+ finally:
460
+ self._results_type = results_type
461
+
462
+ self._descriptions.append(list(mgmt_res.description))
463
+ self._results.append(list(mgmt_res.rows))
464
+ self.rowcount = len(self._results[-1])
465
+
466
+ pymy_res = PyMyResult()
467
+ for field in mgmt_res.fields:
468
+ pymy_res.append(
469
+ PyMyField(
470
+ field.name,
471
+ field.flags,
472
+ field.charsetnr,
473
+ ),
474
+ )
475
+
476
+ self._pymy_results.append(pymy_res)
477
+
478
+ if self._results and self._results[0]:
479
+ self._row_idx = 0
480
+ self._result_idx = 0
481
+
482
+ return self.rowcount
483
+
484
+ def _execute(
485
+ self, oper: str,
486
+ params: Optional[Union[Sequence[Any], Dict[str, Any]]] = None,
487
+ is_callproc: bool = False,
488
+ ) -> int:
489
+ self._descriptions = []
490
+ self._results = []
491
+ self._pymy_results = []
492
+ self._row_idx = -1
493
+ self._result_idx = -1
494
+ self.rowcount = 0
495
+ self._expect_results = False
496
+
497
+ if self._connection is None:
498
+ raise ProgrammingError(errno=2048, msg='Connection is closed.')
499
+
500
+ sql_type = 'exec'
501
+ if re.match(r'^\s*(select|show|call|echo|describe|with)\s+', oper, flags=re.I):
502
+ self._expect_results = True
503
+ sql_type = 'query'
504
+
505
+ self._validate_param_subs(oper, params)
506
+
507
+ handler = fusion.get_handler(oper)
508
+ if handler is not None:
509
+ return self._execute_fusion_query(oper, params, handler=handler)
510
+
511
+ oper, params = self._connection._convert_params(oper, params)
512
+
513
+ log_query(oper, params)
514
+
515
+ data: Dict[str, Any] = dict(sql=oper)
516
+ if params is not None:
517
+ data['args'] = convert_special_params(
518
+ params,
519
+ nan_as_null=self._connection.connection_params['nan_as_null'],
520
+ inf_as_null=self._connection.connection_params['inf_as_null'],
521
+ )
522
+ if self._connection._database:
523
+ data['database'] = self._connection._database
524
+
525
+ if sql_type == 'query':
526
+ res = self._post('query/tuples', json=data)
527
+ else:
528
+ res = self._post('exec', json=data)
529
+
530
+ if res.status_code >= 400:
531
+ if res.text:
532
+ if re.match(r'^Error\s+\d+:', res.text):
533
+ code, msg = res.text.split(':', 1)
534
+ icode = int(code.split()[-1])
535
+ else:
536
+ icode = res.status_code
537
+ msg = res.text
538
+ raise get_exc_type(icode)(icode, msg.strip())
539
+ raise InterfaceError(errno=res.status_code, msg='HTTP Error')
540
+
541
+ out = json.loads(res.text)
542
+
543
+ if 'error' in out:
544
+ raise OperationalError(
545
+ errno=out['error'].get('code', 0),
546
+ msg=out['error'].get('message', 'HTTP Error'),
547
+ )
548
+
549
+ if sql_type == 'query':
550
+ # description: (name, type_code, display_size, internal_size,
551
+ # precision, scale, null_ok, column_flags, charset)
552
+
553
+ # Remove converters for things the JSON parser already converted
554
+ http_converters = dict(self._connection.decoders)
555
+ http_converters.pop(4, None)
556
+ http_converters.pop(5, None)
557
+ http_converters.pop(6, None)
558
+ http_converters.pop(15, None)
559
+ http_converters.pop(245, None)
560
+ http_converters.pop(247, None)
561
+ http_converters.pop(249, None)
562
+ http_converters.pop(250, None)
563
+ http_converters.pop(251, None)
564
+ http_converters.pop(252, None)
565
+ http_converters.pop(253, None)
566
+ http_converters.pop(254, None)
567
+
568
+ # Merge passed in converters
569
+ if self._connection._conv:
570
+ for k, v in self._connection._conv.items():
571
+ if isinstance(k, int):
572
+ http_converters[k] = v
573
+
574
+ results = out['results']
575
+
576
+ # Convert data to Python types
577
+ if results and results[0]:
578
+ self._row_idx = 0
579
+ self._result_idx = 0
580
+
581
+ for result in results:
582
+
583
+ pymy_res = PyMyResult()
584
+ convs = []
585
+
586
+ description: List[Description] = []
587
+ for i, col in enumerate(result.get('columns', [])):
588
+ charset = 0
589
+ flags = 0
590
+ data_type = col['dataType'].split('(')[0]
591
+ type_code = types.ColumnType.get_code(data_type)
592
+ prec, scale = get_precision_scale(col['dataType'])
593
+ converter = http_converters.get(type_code, None)
594
+ if 'UNSIGNED' in data_type:
595
+ flags = 32
596
+ if data_type.endswith('BLOB') or data_type.endswith('BINARY'):
597
+ converter = functools.partial(b64decode_converter, converter)
598
+ charset = 63 # BINARY
599
+ if type_code == 0: # DECIMAL
600
+ type_code = types.ColumnType.get_code('NEWDECIMAL')
601
+ elif type_code == 15: # VARCHAR / VARBINARY
602
+ type_code = types.ColumnType.get_code('VARSTRING')
603
+ if type_code == 246 and prec is not None: # NEWDECIMAL
604
+ prec += 1 # for sign
605
+ if scale is not None and scale > 0:
606
+ prec += 1 # for decimal
607
+ if converter is not None:
608
+ convs.append((i, None, converter))
609
+ description.append(
610
+ Description(
611
+ str(col['name']), type_code,
612
+ None, None, prec, scale,
613
+ col.get('nullable', False),
614
+ flags, charset,
615
+ ),
616
+ )
617
+ pymy_res.append(PyMyField(col['name'], flags, charset))
618
+ self._descriptions.append(description)
619
+
620
+ rows = convert_rows(result.get('rows', []), convs)
621
+
622
+ self._results.append(rows)
623
+ self._pymy_results.append(pymy_res)
624
+
625
+ # For compatibility with PyMySQL/MySQLdb
626
+ if is_callproc:
627
+ self._results.append([])
628
+
629
+ self.rowcount = len(self._results[0])
630
+
631
+ else:
632
+ # For compatibility with PyMySQL/MySQLdb
633
+ if is_callproc:
634
+ self._results.append([])
635
+
636
+ self.rowcount = out['rowsAffected']
637
+
638
+ return self.rowcount
639
+
640
+ def executemany(
641
+ self, query: str,
642
+ args: Optional[Sequence[Union[Sequence[Any], Dict[str, Any]]]] = None,
643
+ ) -> int:
644
+ """
645
+ Execute SQL code against multiple sets of parameters.
646
+
647
+ Parameters
648
+ ----------
649
+ query : str
650
+ The SQL statement to execute
651
+ args : iterable of iterables or dicts, optional
652
+ Sets of parameters to substitute into the SQL code
653
+
654
+ """
655
+ if self._connection is None:
656
+ raise ProgrammingError(errno=2048, msg='Connection is closed.')
657
+
658
+ results = []
659
+ rowcount = 0
660
+ if args is not None and len(args) > 0:
661
+ description = []
662
+ # Detect dataframes
663
+ if hasattr(args, 'itertuples'):
664
+ argiter = args.itertuples(index=False) # type: ignore
665
+ else:
666
+ argiter = iter(args)
667
+ for params in argiter:
668
+ self.execute(query, params)
669
+ if self._descriptions:
670
+ description = self._descriptions[-1]
671
+ if self._rows is not None:
672
+ results.append(self._rows)
673
+ rowcount += self.rowcount
674
+ self._results = results
675
+ self._descriptions = [description for _ in range(len(results))]
676
+ else:
677
+ self.execute(query)
678
+ rowcount += self.rowcount
679
+
680
+ self.rowcount = rowcount
681
+
682
+ return self.rowcount
683
+
684
+ @property
685
+ def _has_row(self) -> bool:
686
+ """Determine if a row is available."""
687
+ if self._result_idx < 0 or self._result_idx >= len(self._results):
688
+ return False
689
+ if self._row_idx < 0 or self._row_idx >= len(self._results[self._result_idx]):
690
+ return False
691
+ return True
692
+
693
+ @property
694
+ def _rows(self) -> List[Tuple[Any, ...]]:
695
+ """Return current set of rows."""
696
+ if not self._has_row:
697
+ return []
698
+ return self._results[self._result_idx]
699
+
700
+ def fetchone(self) -> Optional[Result]:
701
+ """
702
+ Fetch a single row from the result set.
703
+
704
+ Returns
705
+ -------
706
+ tuple
707
+ Values of the returned row if there are rows remaining
708
+ None
709
+ If there are no rows left to return
710
+
711
+ """
712
+ if self._connection is None:
713
+ raise ProgrammingError(errno=2048, msg='Connection is closed')
714
+ if not self._expect_results:
715
+ raise self._connection.ProgrammingError(msg='No query has been submitted')
716
+ if not self._has_row:
717
+ return None
718
+ out = self._rows[self._row_idx]
719
+ self._row_idx += 1
720
+ return format_results(
721
+ self._results_type,
722
+ self.description or [],
723
+ out, single=True,
724
+ )
725
+
726
+ def fetchmany(
727
+ self,
728
+ size: Optional[int] = None,
729
+ ) -> Result:
730
+ """
731
+ Fetch `size` rows from the result.
732
+
733
+ If `size` is not specified, the `arraysize` attribute is used.
734
+
735
+ Returns
736
+ -------
737
+ list of tuples
738
+ Values of the returned rows if there are rows remaining
739
+
740
+ """
741
+ if self._connection is None:
742
+ raise ProgrammingError(errno=2048, msg='Connection is closed')
743
+ if not self._expect_results:
744
+ raise self._connection.ProgrammingError(msg='No query has been submitted')
745
+ if not self._has_row:
746
+ if 'dict' in self._results_type:
747
+ return {}
748
+ return tuple()
749
+ if not size:
750
+ size = max(int(self.arraysize), 1)
751
+ else:
752
+ size = max(int(size), 1)
753
+ out = self._rows[self._row_idx:self._row_idx+size]
754
+ self._row_idx += len(out)
755
+ return format_results(self._results_type, self.description or [], out)
756
+
757
+ def fetchall(self) -> Result:
758
+ """
759
+ Fetch all rows in the result set.
760
+
761
+ Returns
762
+ -------
763
+ list of tuples
764
+ Values of the returned rows if there are rows remaining
765
+
766
+ """
767
+ if self._connection is None:
768
+ raise ProgrammingError(errno=2048, msg='Connection is closed')
769
+ if not self._expect_results:
770
+ raise self._connection.ProgrammingError(msg='No query has been submitted')
771
+ if not self._has_row:
772
+ if 'dict' in self._results_type:
773
+ return {}
774
+ return tuple()
775
+ out = list(self._rows[self._row_idx:])
776
+ self._row_idx = len(out)
777
+ return format_results(self._results_type, self.description or [], out)
778
+
779
+ def nextset(self) -> Optional[bool]:
780
+ """Skip to the next available result set."""
781
+ if self._connection is None:
782
+ raise ProgrammingError(errno=2048, msg='Connection is closed')
783
+
784
+ if self._result_idx < 0:
785
+ self._row_idx = -1
786
+ return None
787
+
788
+ self._result_idx += 1
789
+ self._row_idx = 0
790
+
791
+ if self._result_idx >= len(self._results):
792
+ self._result_idx = -1
793
+ self._row_idx = -1
794
+ return None
795
+
796
+ self.rowcount = len(self._results[self._result_idx])
797
+
798
+ return True
799
+
800
+ def setinputsizes(self, sizes: Sequence[int]) -> None:
801
+ """Predefine memory areas for parameters."""
802
+ pass
803
+
804
+ def setoutputsize(self, size: int, column: Optional[str] = None) -> None:
805
+ """Set a column buffer size for fetches of large columns."""
806
+ pass
807
+
808
+ @property
809
+ def rownumber(self) -> Optional[int]:
810
+ """
811
+ Return the zero-based index of the cursor in the result set.
812
+
813
+ Returns
814
+ -------
815
+ int
816
+
817
+ """
818
+ if self._row_idx < 0:
819
+ return None
820
+ return self._row_idx
821
+
822
+ def scroll(self, value: int, mode: str = 'relative') -> None:
823
+ """
824
+ Scroll the cursor to the position in the result set.
825
+
826
+ Parameters
827
+ ----------
828
+ value : int
829
+ Value of the positional move
830
+ mode : str
831
+ Type of move that should be made: 'relative' or 'absolute'
832
+
833
+ """
834
+ if self._connection is None:
835
+ raise ProgrammingError(errno=2048, msg='Connection is closed')
836
+ if mode == 'relative':
837
+ self._row_idx += value
838
+ elif mode == 'absolute':
839
+ self._row_idx = value
840
+ else:
841
+ raise ValueError(
842
+ f'{mode} is not a valid mode, '
843
+ 'expecting "relative" or "absolute"',
844
+ )
845
+
846
+ def next(self) -> Optional[Result]:
847
+ """
848
+ Return the next row from the result set for use in iterators.
849
+
850
+ Returns
851
+ -------
852
+ tuple
853
+ Values from the next result row
854
+ None
855
+ If no more rows exist
856
+
857
+ """
858
+ if self._connection is None:
859
+ raise InterfaceError(errno=2048, msg='Connection is closed')
860
+ out = self.fetchone()
861
+ if out is None:
862
+ raise StopIteration
863
+ return out
864
+
865
+ __next__ = next
866
+
867
+ def __iter__(self) -> Iterable[Tuple[Any, ...]]:
868
+ """Return result iterator."""
869
+ return iter(self._rows)
870
+
871
+ def __enter__(self) -> 'Cursor':
872
+ """Enter a context."""
873
+ return self
874
+
875
+ def __exit__(
876
+ self, exc_type: Optional[object],
877
+ exc_value: Optional[Exception], exc_traceback: Optional[str],
878
+ ) -> None:
879
+ """Exit a context."""
880
+ self.close()
881
+
882
+ @property
883
+ def open(self) -> bool:
884
+ """Check if the cursor is still connected."""
885
+ if self._connection is None:
886
+ return False
887
+ return self._connection.is_connected()
888
+
889
+ def is_connected(self) -> bool:
890
+ """
891
+ Check if the cursor is still connected.
892
+
893
+ Returns
894
+ -------
895
+ bool
896
+
897
+ """
898
+ return self.open
899
+
900
+
901
+ class Connection(connection.Connection):
902
+ """
903
+ SingleStoreDB HTTP database connection.
904
+
905
+ Instances of this object are typically created through the
906
+ `connection` function rather than creating them directly.
907
+
908
+ See Also
909
+ --------
910
+ `connect`
911
+
912
+ """
913
+ driver = 'https'
914
+ paramstyle = 'qmark'
915
+
916
+ def __init__(self, **kwargs: Any):
917
+ from .. import __version__ as client_version
918
+ connection.Connection.__init__(self, **kwargs)
919
+
920
+ host = kwargs.get('host', get_option('host'))
921
+ port = kwargs.get('port', get_option('http_port'))
922
+
923
+ self._sess: Optional[requests.Session] = requests.Session()
924
+
925
+ user = kwargs.get('user', get_option('user'))
926
+ password = kwargs.get('password', get_option('password'))
927
+ if user is not None and password is not None:
928
+ self._sess.auth = (user, password)
929
+ elif user is not None:
930
+ self._sess.auth = (user, '')
931
+ self._sess.headers.update({
932
+ 'Content-Type': 'application/json',
933
+ 'Accept': 'application/json',
934
+ 'Accept-Encoding': 'compress,identity',
935
+ 'User-Agent': f'SingleStoreDB-Python/{client_version}',
936
+ })
937
+
938
+ if kwargs.get('ssl_disabled', get_option('ssl_disabled')):
939
+ self._sess.verify = False
940
+ else:
941
+ ssl_key = kwargs.get('ssl_key', get_option('ssl_key'))
942
+ ssl_cert = kwargs.get('ssl_cert', get_option('ssl_cert'))
943
+ if ssl_key and ssl_cert:
944
+ self._sess.cert = (ssl_key, ssl_cert)
945
+ elif ssl_cert:
946
+ self._sess.cert = ssl_cert
947
+
948
+ ssl_ca = kwargs.get('ssl_ca', get_option('ssl_ca'))
949
+ if ssl_ca:
950
+ self._sess.verify = ssl_ca
951
+
952
+ ssl_verify_cert = kwargs.get('ssl_verify_cert', True)
953
+ if not ssl_verify_cert:
954
+ self._sess.verify = False
955
+
956
+ if kwargs.get('multi_statements', False):
957
+ raise self.InterfaceError(
958
+ 0, 'The Data API does not allow multiple '
959
+ 'statements within a query',
960
+ )
961
+
962
+ self._version = kwargs.get('version', 'v2')
963
+ self.driver = kwargs.get('driver', 'https')
964
+
965
+ self.encoders = {k: v for (k, v) in converters.items() if type(k) is not int}
966
+ self.decoders = {k: v for (k, v) in converters.items() if type(k) is int}
967
+
968
+ self._database = kwargs.get('database', get_option('database'))
969
+ self._url = f'{self.driver}://{host}:{port}/api/{self._version}/'
970
+ self._host = host
971
+ self._messages: List[Tuple[int, str]] = []
972
+ self._autocommit: bool = True
973
+ self._conv = kwargs.get('conv', None)
974
+ self._in_sync: bool = False
975
+ self._track_env: bool = kwargs.get('track_env', False) \
976
+ or host == 'singlestore.com'
977
+
978
+ @property
979
+ def messages(self) -> List[Tuple[int, str]]:
980
+ return self._messages
981
+
982
+ def connect(self) -> 'Connection':
983
+ """Connect to the server."""
984
+ return self
985
+
986
+ def _sync_connection(self, kwargs: Dict[str, Any]) -> None:
987
+ """Synchronize connection with env variable."""
988
+ if self._sess is None:
989
+ raise InterfaceError(errno=2048, msg='Connection is closed.')
990
+
991
+ if self._in_sync:
992
+ return
993
+
994
+ if not self._track_env:
995
+ return
996
+
997
+ url = os.environ.get('SINGLESTOREDB_URL')
998
+ if not url:
999
+ if self._host == 'singlestore.com':
1000
+ raise InterfaceError(0, 'Connection URL has not been established')
1001
+ return
1002
+
1003
+ out = {}
1004
+ urlp = connection._parse_url(url)
1005
+ out.update(urlp)
1006
+ out = connection._cast_params(out)
1007
+
1008
+ # Set default port based on driver.
1009
+ if 'port' not in out or not out['port']:
1010
+ if out.get('driver', 'https') == 'http':
1011
+ out['port'] = int(get_option('port') or 80)
1012
+ else:
1013
+ out['port'] = int(get_option('port') or 443)
1014
+
1015
+ # If there is no user and the password is empty, remove the password key.
1016
+ if 'user' not in out and not out.get('password', None):
1017
+ out.pop('password', None)
1018
+
1019
+ if out['host'] == 'singlestore.com':
1020
+ raise InterfaceError(0, 'Connection URL has not been established')
1021
+
1022
+ # Get current connection attributes
1023
+ curr_url = urlparse(self._url, scheme='singlestoredb', allow_fragments=True)
1024
+ if self._sess.auth is not None:
1025
+ auth = tuple(self._sess.auth) # type: ignore
1026
+ else:
1027
+ auth = (None, None) # type: ignore
1028
+
1029
+ # If it's just a password change, we don't need to reconnect
1030
+ if (curr_url.hostname, curr_url.port, auth[0], self._database) == \
1031
+ (out['host'], out['port'], out['user'], out.get('database')):
1032
+ return
1033
+
1034
+ try:
1035
+ self._in_sync = True
1036
+ sess = requests.Session()
1037
+ sess.auth = (out['user'], out['password'])
1038
+ sess.headers.update(self._sess.headers)
1039
+ sess.verify = self._sess.verify
1040
+ sess.cert = self._sess.cert
1041
+ self._database = out.get('database')
1042
+ self._host = out['host']
1043
+ self._url = f'{out.get("driver", "https")}://{out["host"]}:{out["port"]}' \
1044
+ f'/api/{self._version}/'
1045
+ self._sess = sess
1046
+ if self._database:
1047
+ kwargs['json']['database'] = self._database
1048
+ finally:
1049
+ self._in_sync = False
1050
+
1051
+ def _post(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
1052
+ """
1053
+ Invoke a POST request on the HTTP connection.
1054
+
1055
+ Parameters
1056
+ ----------
1057
+ path : str
1058
+ The path of the resource
1059
+ *args : positional parameters, optional
1060
+ Extra parameters to the POST request
1061
+ **kwargs : keyword parameters, optional
1062
+ Extra keyword parameters to the POST request
1063
+
1064
+ Returns
1065
+ -------
1066
+ requests.Response
1067
+
1068
+ """
1069
+ if self._sess is None:
1070
+ raise InterfaceError(errno=2048, msg='Connection is closed.')
1071
+
1072
+ self._sync_connection(kwargs)
1073
+
1074
+ if 'timeout' not in kwargs:
1075
+ kwargs['timeout'] = get_option('connect_timeout')
1076
+
1077
+ return self._sess.post(urljoin(self._url, path), *args, **kwargs)
1078
+
1079
+ def close(self) -> None:
1080
+ """Close the connection."""
1081
+ if self._host == 'singlestore.com':
1082
+ return
1083
+ if self._sess is None:
1084
+ raise Error(errno=2048, msg='Connection is closed')
1085
+ self._sess = None
1086
+
1087
+ def autocommit(self, value: bool = True) -> None:
1088
+ """Set autocommit mode."""
1089
+ if self._host == 'singlestore.com':
1090
+ return
1091
+ if self._sess is None:
1092
+ raise InterfaceError(errno=2048, msg='Connection is closed')
1093
+ self._autocommit = value
1094
+
1095
+ def commit(self) -> None:
1096
+ """Commit the pending transaction."""
1097
+ if self._host == 'singlestore.com':
1098
+ return
1099
+ if self._sess is None:
1100
+ raise InterfaceError(errno=2048, msg='Connection is closed')
1101
+ if self._autocommit:
1102
+ return
1103
+ raise NotSupportedError(msg='operation not supported')
1104
+
1105
+ def rollback(self) -> None:
1106
+ """Rollback the pending transaction."""
1107
+ if self._host == 'singlestore.com':
1108
+ return
1109
+ if self._sess is None:
1110
+ raise InterfaceError(errno=2048, msg='Connection is closed')
1111
+ if self._autocommit:
1112
+ return
1113
+ raise NotSupportedError(msg='operation not supported')
1114
+
1115
+ def cursor(self) -> Cursor:
1116
+ """
1117
+ Create a new cursor object.
1118
+
1119
+ Returns
1120
+ -------
1121
+ Cursor
1122
+
1123
+ """
1124
+ return Cursor(self)
1125
+
1126
+ def __enter__(self) -> 'Connection':
1127
+ """Enter a context."""
1128
+ return self
1129
+
1130
+ def __exit__(
1131
+ self, exc_type: Optional[object],
1132
+ exc_value: Optional[Exception], exc_traceback: Optional[str],
1133
+ ) -> None:
1134
+ """Exit a context."""
1135
+ self.close()
1136
+
1137
+ @property
1138
+ def open(self) -> bool:
1139
+ """Check if the database is still connected."""
1140
+ if self._sess is None:
1141
+ return False
1142
+ url = '/'.join(self._url.split('/')[:3]) + '/ping'
1143
+ res = self._sess.get(url)
1144
+ if res.status_code <= 400 and res.text == 'pong':
1145
+ return True
1146
+ return False
1147
+
1148
+ def is_connected(self) -> bool:
1149
+ """
1150
+ Check if the database is still connected.
1151
+
1152
+ Returns
1153
+ -------
1154
+ bool
1155
+
1156
+ """
1157
+ return self.open
1158
+
1159
+
1160
+ def connect(
1161
+ host: Optional[str] = None,
1162
+ user: Optional[str] = None,
1163
+ password: Optional[str] = None,
1164
+ port: Optional[int] = None,
1165
+ database: Optional[str] = None,
1166
+ driver: Optional[str] = None,
1167
+ pure_python: Optional[bool] = None,
1168
+ local_infile: Optional[bool] = None,
1169
+ charset: Optional[str] = None,
1170
+ ssl_key: Optional[str] = None,
1171
+ ssl_cert: Optional[str] = None,
1172
+ ssl_ca: Optional[str] = None,
1173
+ ssl_disabled: Optional[bool] = None,
1174
+ ssl_cipher: Optional[str] = None,
1175
+ ssl_verify_cert: Optional[bool] = None,
1176
+ ssl_verify_identity: Optional[bool] = None,
1177
+ conv: Optional[Dict[int, Callable[..., Any]]] = None,
1178
+ credential_type: Optional[str] = None,
1179
+ autocommit: Optional[bool] = None,
1180
+ results_type: Optional[str] = None,
1181
+ buffered: Optional[bool] = None,
1182
+ results_format: Optional[str] = None,
1183
+ program_name: Optional[str] = None,
1184
+ conn_attrs: Optional[Dict[str, str]] = None,
1185
+ multi_statements: Optional[bool] = None,
1186
+ connect_timeout: Optional[int] = None,
1187
+ nan_as_null: Optional[bool] = None,
1188
+ inf_as_null: Optional[bool] = None,
1189
+ encoding_errors: Optional[str] = None,
1190
+ track_env: Optional[bool] = None,
1191
+ ) -> Connection:
1192
+ return Connection(**dict(locals()))