clickhouse-driver 0.2.10__cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.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 (89) hide show
  1. clickhouse_driver/__init__.py +9 -0
  2. clickhouse_driver/block.py +227 -0
  3. clickhouse_driver/blockstreamprofileinfo.py +22 -0
  4. clickhouse_driver/bufferedreader.cpython-310-aarch64-linux-gnu.so +0 -0
  5. clickhouse_driver/bufferedwriter.cpython-310-aarch64-linux-gnu.so +0 -0
  6. clickhouse_driver/client.py +812 -0
  7. clickhouse_driver/clientinfo.py +119 -0
  8. clickhouse_driver/columns/__init__.py +0 -0
  9. clickhouse_driver/columns/arraycolumn.py +161 -0
  10. clickhouse_driver/columns/base.py +221 -0
  11. clickhouse_driver/columns/boolcolumn.py +7 -0
  12. clickhouse_driver/columns/datecolumn.py +108 -0
  13. clickhouse_driver/columns/datetimecolumn.py +203 -0
  14. clickhouse_driver/columns/decimalcolumn.py +116 -0
  15. clickhouse_driver/columns/enumcolumn.py +129 -0
  16. clickhouse_driver/columns/exceptions.py +12 -0
  17. clickhouse_driver/columns/floatcolumn.py +34 -0
  18. clickhouse_driver/columns/intcolumn.py +157 -0
  19. clickhouse_driver/columns/intervalcolumn.py +33 -0
  20. clickhouse_driver/columns/ipcolumn.py +118 -0
  21. clickhouse_driver/columns/jsoncolumn.py +37 -0
  22. clickhouse_driver/columns/largeint.cpython-310-aarch64-linux-gnu.so +0 -0
  23. clickhouse_driver/columns/lowcardinalitycolumn.py +142 -0
  24. clickhouse_driver/columns/mapcolumn.py +73 -0
  25. clickhouse_driver/columns/nestedcolumn.py +10 -0
  26. clickhouse_driver/columns/nothingcolumn.py +13 -0
  27. clickhouse_driver/columns/nullablecolumn.py +7 -0
  28. clickhouse_driver/columns/nullcolumn.py +15 -0
  29. clickhouse_driver/columns/numpy/__init__.py +0 -0
  30. clickhouse_driver/columns/numpy/base.py +47 -0
  31. clickhouse_driver/columns/numpy/boolcolumn.py +8 -0
  32. clickhouse_driver/columns/numpy/datecolumn.py +19 -0
  33. clickhouse_driver/columns/numpy/datetimecolumn.py +146 -0
  34. clickhouse_driver/columns/numpy/floatcolumn.py +24 -0
  35. clickhouse_driver/columns/numpy/intcolumn.py +43 -0
  36. clickhouse_driver/columns/numpy/lowcardinalitycolumn.py +96 -0
  37. clickhouse_driver/columns/numpy/service.py +58 -0
  38. clickhouse_driver/columns/numpy/stringcolumn.py +78 -0
  39. clickhouse_driver/columns/numpy/tuplecolumn.py +37 -0
  40. clickhouse_driver/columns/service.py +185 -0
  41. clickhouse_driver/columns/simpleaggregatefunctioncolumn.py +7 -0
  42. clickhouse_driver/columns/stringcolumn.py +73 -0
  43. clickhouse_driver/columns/tuplecolumn.py +63 -0
  44. clickhouse_driver/columns/util.py +61 -0
  45. clickhouse_driver/columns/uuidcolumn.py +64 -0
  46. clickhouse_driver/compression/__init__.py +32 -0
  47. clickhouse_driver/compression/base.py +87 -0
  48. clickhouse_driver/compression/lz4.py +21 -0
  49. clickhouse_driver/compression/lz4hc.py +9 -0
  50. clickhouse_driver/compression/zstd.py +20 -0
  51. clickhouse_driver/connection.py +825 -0
  52. clickhouse_driver/context.py +36 -0
  53. clickhouse_driver/dbapi/__init__.py +62 -0
  54. clickhouse_driver/dbapi/connection.py +99 -0
  55. clickhouse_driver/dbapi/cursor.py +370 -0
  56. clickhouse_driver/dbapi/errors.py +40 -0
  57. clickhouse_driver/dbapi/extras.py +73 -0
  58. clickhouse_driver/defines.py +58 -0
  59. clickhouse_driver/errors.py +453 -0
  60. clickhouse_driver/log.py +48 -0
  61. clickhouse_driver/numpy/__init__.py +0 -0
  62. clickhouse_driver/numpy/block.py +8 -0
  63. clickhouse_driver/numpy/helpers.py +28 -0
  64. clickhouse_driver/numpy/result.py +123 -0
  65. clickhouse_driver/opentelemetry.py +43 -0
  66. clickhouse_driver/progress.py +44 -0
  67. clickhouse_driver/protocol.py +130 -0
  68. clickhouse_driver/queryprocessingstage.py +8 -0
  69. clickhouse_driver/reader.py +69 -0
  70. clickhouse_driver/readhelpers.py +26 -0
  71. clickhouse_driver/result.py +144 -0
  72. clickhouse_driver/settings/__init__.py +0 -0
  73. clickhouse_driver/settings/available.py +405 -0
  74. clickhouse_driver/settings/types.py +50 -0
  75. clickhouse_driver/settings/writer.py +34 -0
  76. clickhouse_driver/streams/__init__.py +0 -0
  77. clickhouse_driver/streams/compressed.py +88 -0
  78. clickhouse_driver/streams/native.py +108 -0
  79. clickhouse_driver/util/__init__.py +0 -0
  80. clickhouse_driver/util/compat.py +39 -0
  81. clickhouse_driver/util/escape.py +94 -0
  82. clickhouse_driver/util/helpers.py +173 -0
  83. clickhouse_driver/varint.cpython-310-aarch64-linux-gnu.so +0 -0
  84. clickhouse_driver/writer.py +67 -0
  85. clickhouse_driver-0.2.10.dist-info/METADATA +215 -0
  86. clickhouse_driver-0.2.10.dist-info/RECORD +89 -0
  87. clickhouse_driver-0.2.10.dist-info/WHEEL +7 -0
  88. clickhouse_driver-0.2.10.dist-info/licenses/LICENSE +21 -0
  89. clickhouse_driver-0.2.10.dist-info/top_level.txt +1 -0
@@ -0,0 +1,36 @@
1
+
2
+ class Context(object):
3
+ def __init__(self):
4
+ self._server_info = None
5
+ self._settings = None
6
+ self._client_settings = None
7
+ super(Context, self).__init__()
8
+
9
+ @property
10
+ def server_info(self):
11
+ return self._server_info
12
+
13
+ @server_info.setter
14
+ def server_info(self, value):
15
+ self._server_info = value
16
+
17
+ @property
18
+ def settings(self):
19
+ return self._settings.copy()
20
+
21
+ @settings.setter
22
+ def settings(self, value):
23
+ self._settings = value.copy()
24
+
25
+ @property
26
+ def client_settings(self):
27
+ return self._client_settings.copy()
28
+
29
+ @client_settings.setter
30
+ def client_settings(self, value):
31
+ self._client_settings = value.copy()
32
+
33
+ def __repr__(self):
34
+ return '<Context(server_info=%s, client_settings=%s, settings=%s)>' % (
35
+ self._server_info, self._client_settings, self._settings
36
+ )
@@ -0,0 +1,62 @@
1
+ from .connection import Connection
2
+ from .errors import (
3
+ Warning, Error, DataError, DatabaseError, ProgrammingError, IntegrityError,
4
+ InterfaceError, InternalError, NotSupportedError, OperationalError
5
+ )
6
+ from .. import defines
7
+
8
+ apilevel = '2.0'
9
+
10
+ threadsafety = 2
11
+
12
+ paramstyle = 'pyformat'
13
+
14
+
15
+ def connect(dsn=None, host=None,
16
+ user=defines.DEFAULT_USER, password=defines.DEFAULT_PASSWORD,
17
+ port=defines.DEFAULT_PORT, database=defines.DEFAULT_DATABASE,
18
+ **kwargs):
19
+ """
20
+ Create a new database connection.
21
+
22
+ The connection can be specified via DSN:
23
+
24
+ ``conn = connect("clickhouse://localhost/test?param1=value1&...")``
25
+
26
+ or using database and credentials arguments:
27
+
28
+ ``conn = connect(database="test", user="default", password="default",
29
+ host="localhost", **kwargs)``
30
+
31
+ The basic connection parameters are:
32
+
33
+ - *host*: host with running ClickHouse server.
34
+ - *port*: port ClickHouse server is bound to.
35
+ - *database*: database connect to.
36
+ - *user*: database user.
37
+ - *password*: user's password.
38
+
39
+ See defaults in :data:`~clickhouse_driver.connection.Connection`
40
+ constructor.
41
+
42
+ DSN or host is required.
43
+
44
+ Any other keyword parameter will be passed to the underlying Connection
45
+ class.
46
+
47
+ :return: a new connection.
48
+ """
49
+
50
+ if dsn is None and host is None:
51
+ raise ValueError('host or dsn is required')
52
+
53
+ return Connection(dsn=dsn, user=user, password=password, host=host,
54
+ port=port, database=database, **kwargs)
55
+
56
+
57
+ __all__ = [
58
+ 'connect',
59
+ 'Warning', 'Error', 'DataError', 'DatabaseError', 'ProgrammingError',
60
+ 'IntegrityError', 'InterfaceError', 'InternalError', 'NotSupportedError',
61
+ 'OperationalError'
62
+ ]
@@ -0,0 +1,99 @@
1
+ from ..client import Client
2
+ from .. import defines
3
+ from .cursor import Cursor
4
+ from .errors import InterfaceError
5
+
6
+
7
+ class Connection(object):
8
+ """
9
+ Creates new Connection for accessing ClickHouse database.
10
+
11
+ Connection is just wrapper for handling multiple cursors (clients) and
12
+ do not initiate actual connections to the ClickHouse server.
13
+
14
+ See parameters description in
15
+ :data:`~clickhouse_driver.connection.Connection`.
16
+ """
17
+ def __init__(self, dsn=None, host=None,
18
+ user=defines.DEFAULT_USER, password=defines.DEFAULT_PASSWORD,
19
+ port=defines.DEFAULT_PORT, database=defines.DEFAULT_DATABASE,
20
+ **kwargs):
21
+ self.cursors = []
22
+
23
+ self.dsn = dsn
24
+ self.user = user
25
+ self.password = password
26
+ self.host = host
27
+ self.port = port
28
+ self.database = database
29
+ self.connection_kwargs = kwargs
30
+ self.is_closed = False
31
+ self._hosts = None
32
+ super(Connection, self).__init__()
33
+
34
+ def __repr__(self):
35
+ return '<connection object at 0x{0:x}; closed: {1:}>'.format(
36
+ id(self), self.is_closed
37
+ )
38
+
39
+ # Context manager integrations.
40
+ def __enter__(self):
41
+ return self
42
+
43
+ def __exit__(self, exc_type, exc_val, exc_tb):
44
+ self.close()
45
+
46
+ def _make_client(self):
47
+ """
48
+ :return: a new Client instance.
49
+ """
50
+ if self.dsn is not None:
51
+ return Client.from_url(self.dsn)
52
+
53
+ return Client(self.host, port=self.port,
54
+ user=self.user, password=self.password,
55
+ database=self.database, **self.connection_kwargs)
56
+
57
+ def close(self):
58
+ """
59
+ Close the connection now. The connection will be unusable from this
60
+ point forward; an :data:`~clickhouse_driver.dbapi.Error` (or subclass)
61
+ exception will be raised if any operation is attempted with the
62
+ connection. The same applies to all cursor objects trying to use the
63
+ connection.
64
+ """
65
+ for cursor in self.cursors:
66
+ cursor.close()
67
+
68
+ self.is_closed = True
69
+
70
+ def commit(self):
71
+ """
72
+ Do nothing since ClickHouse has no transactions.
73
+ """
74
+ pass
75
+
76
+ def rollback(self):
77
+ """
78
+ Do nothing since ClickHouse has no transactions.
79
+ """
80
+ pass
81
+
82
+ def cursor(self, cursor_factory=None):
83
+ """
84
+ :param cursor_factory: Argument can be used to create non-standard
85
+ cursors.
86
+ :return: a new cursor object using the connection.
87
+ """
88
+ if self.is_closed:
89
+ raise InterfaceError('connection already closed')
90
+
91
+ client = self._make_client()
92
+ if self._hosts is None:
93
+ self._hosts = client.connection.hosts
94
+ else:
95
+ client.connection.hosts = self._hosts
96
+ cursor_factory = cursor_factory or Cursor
97
+ cursor = cursor_factory(client, self)
98
+ self.cursors.append(cursor)
99
+ return cursor
@@ -0,0 +1,370 @@
1
+ from collections import namedtuple
2
+ from itertools import islice
3
+
4
+ from ..errors import Error as DriverError
5
+ from .errors import InterfaceError, OperationalError, ProgrammingError
6
+
7
+
8
+ Column = namedtuple(
9
+ 'Column',
10
+ 'name type_code display_size internal_size precision scale null_ok'
11
+ )
12
+
13
+
14
+ class Cursor(object):
15
+
16
+ class States(object):
17
+ (
18
+ NONE,
19
+ RUNNING,
20
+ FINISHED,
21
+ CURSOR_CLOSED
22
+ ) = range(4)
23
+
24
+ _states = States()
25
+
26
+ def __init__(self, client, connection):
27
+ self._client = client
28
+ self._connection = connection
29
+ self._reset_state()
30
+
31
+ self.arraysize = 1
32
+
33
+ # Begin non-PEP attributes
34
+ self._columns_with_types = None
35
+ # End non-PEP attributes
36
+
37
+ super(Cursor, self).__init__()
38
+
39
+ def __repr__(self):
40
+ is_closed = self._state == self._states.CURSOR_CLOSED
41
+ return '<cursor object at 0x{0:x}; closed: {1:}>'.format(
42
+ id(self), is_closed
43
+ )
44
+
45
+ # Iteration support.
46
+ def __iter__(self):
47
+ while True:
48
+ one = self.fetchone()
49
+ if one is None:
50
+ return
51
+ yield one
52
+
53
+ # Context manager integrations.
54
+ def __enter__(self):
55
+ return self
56
+
57
+ def __exit__(self, exc_type, exc_val, exc_tb):
58
+ self.close()
59
+
60
+ @property
61
+ def description(self):
62
+ if self._state == self._states.NONE:
63
+ return None
64
+
65
+ columns = self._columns or []
66
+ types = self._types or []
67
+
68
+ return [
69
+ Column(name, type_code, None, None, None, None, True)
70
+ for name, type_code in zip(columns, types)
71
+ ]
72
+
73
+ @property
74
+ def rowcount(self):
75
+ """
76
+ :return: the number of rows that the last .execute*() produced.
77
+ """
78
+ return self._rowcount
79
+
80
+ def close(self):
81
+ """
82
+ Close the cursor now. The cursor will be unusable from this point
83
+ forward; an :data:`~clickhouse_driver.dbapi.Error` (or subclass)
84
+ exception will be raised if any operation is attempted with the
85
+ cursor.
86
+ """
87
+ self._client.disconnect()
88
+ self._state = self._states.CURSOR_CLOSED
89
+
90
+ try:
91
+ # cursor can be already closed
92
+ self._connection.cursors.remove(self)
93
+ except ValueError:
94
+ pass
95
+
96
+ def execute(self, operation, parameters=None):
97
+ """
98
+ Prepare and execute a database operation (query or command).
99
+
100
+ :param operation: query or command to execute.
101
+ :param parameters: sequence or mapping that will be bound to
102
+ variables in the operation.
103
+ :return: None
104
+ """
105
+ self._check_cursor_closed()
106
+ self._begin_query()
107
+
108
+ try:
109
+ execute, execute_kwargs = self._prepare()
110
+
111
+ response = execute(
112
+ operation, params=parameters, with_column_types=True,
113
+ **execute_kwargs
114
+ )
115
+
116
+ except DriverError as orig:
117
+ raise OperationalError(orig)
118
+
119
+ self._process_response(response)
120
+ self._end_query()
121
+
122
+ def executemany(self, operation, seq_of_parameters):
123
+ """
124
+ Prepare a database operation (query or command) and then execute it
125
+ against all parameter sequences found in the sequence
126
+ `seq_of_parameters`.
127
+
128
+ :param operation: query or command to execute.
129
+ :param seq_of_parameters: sequences or mappings for execution.
130
+ :return: None
131
+ """
132
+ self._check_cursor_closed()
133
+ self._begin_query()
134
+
135
+ try:
136
+ execute, execute_kwargs = self._prepare()
137
+
138
+ response = execute(
139
+ operation, params=seq_of_parameters, **execute_kwargs
140
+ )
141
+
142
+ except DriverError as orig:
143
+ raise OperationalError(orig)
144
+
145
+ self._process_response(response, executemany=True)
146
+ self._end_query()
147
+
148
+ def fetchone(self):
149
+ """
150
+ Fetch the next row of a query result set, returning a single sequence,
151
+ or None when no more data is available.
152
+
153
+ :return: the next row of a query result set or None.
154
+ """
155
+ self._check_query_started()
156
+
157
+ if self._stream_results:
158
+ return next(self._rows, None)
159
+
160
+ else:
161
+ if not self._rows:
162
+ return None
163
+
164
+ return self._rows.pop(0)
165
+
166
+ def fetchmany(self, size=None):
167
+ """
168
+ Fetch the next set of rows of a query result, returning a sequence of
169
+ sequences (e.g. a list of tuples). An empty sequence is returned when
170
+ no more rows are available.
171
+
172
+ :param size: amount of rows to return.
173
+ :return: list of fetched rows or empty list.
174
+ """
175
+ self._check_query_started()
176
+
177
+ if size is None:
178
+ size = self.arraysize
179
+
180
+ if self._stream_results:
181
+ if size == -1:
182
+ return list(self._rows)
183
+ else:
184
+ return list(islice(self._rows, size))
185
+
186
+ if size < 0:
187
+ rv = self._rows
188
+ self._rows = []
189
+ else:
190
+ rv = self._rows[:size]
191
+ self._rows = self._rows[size:]
192
+
193
+ return rv
194
+
195
+ def fetchall(self):
196
+ """
197
+ Fetch all (remaining) rows of a query result, returning them as a
198
+ sequence of sequences (e.g. a list of tuples).
199
+
200
+ :return: list of fetched rows.
201
+ """
202
+ self._check_query_started()
203
+
204
+ if self._stream_results:
205
+ return list(self._rows)
206
+
207
+ rv = self._rows
208
+ self._rows = []
209
+ return rv
210
+
211
+ def setinputsizes(self, sizes):
212
+ # Do nothing.
213
+ pass
214
+
215
+ def setoutputsize(self, size, column=None):
216
+ # Do nothing.
217
+ pass
218
+
219
+ # Begin non-PEP methods
220
+ @property
221
+ def columns_with_types(self):
222
+ """
223
+ :return: list of column names with corresponding types of the last
224
+ .execute*(). E.g. [('x', 'UInt64')].
225
+ """
226
+ return self._columns_with_types
227
+
228
+ def set_stream_results(self, stream_results, max_row_buffer):
229
+ """
230
+ Toggles results streaming from server. Driver will consume
231
+ block-by-block of `max_row_buffer` size and yield row-by-row from each
232
+ block.
233
+
234
+ :param stream_results: enable or disable results streaming.
235
+ :param max_row_buffer: specifies the maximum number of rows to buffer
236
+ at a time.
237
+ :return: None
238
+ """
239
+ self._stream_results = stream_results
240
+ self._max_row_buffer = max_row_buffer
241
+
242
+ def set_settings(self, settings):
243
+ """
244
+ Specifies settings for cursor.
245
+
246
+ :param settings: dictionary of query settings
247
+ :return: None
248
+ """
249
+ self._settings = settings
250
+
251
+ def set_types_check(self, types_check):
252
+ """
253
+ Toggles type checking for sequence of INSERT parameters.
254
+ Disabled by default.
255
+
256
+ :param types_check: new types check value.
257
+ :return: None
258
+ """
259
+ self._types_check = types_check
260
+
261
+ def set_external_table(self, name, structure, data):
262
+ """
263
+ Adds external table to cursor context.
264
+
265
+ If the same table is specified more than once the last one is used.
266
+
267
+ :param name: name of external table
268
+ :param structure: list of tuples (name, type) that defines table
269
+ structure. Example [(x, 'Int32')].
270
+ :param data: sequence of rows of tuples or dicts for transmission.
271
+ :return: None
272
+ """
273
+ self._external_tables[name] = (structure, data)
274
+
275
+ def set_query_id(self, query_id):
276
+ """
277
+ Specifies the query identifier for cursor.
278
+
279
+ :param query_id: the query identifier.
280
+ :return: None
281
+ """
282
+ self._query_id = query_id
283
+ # End non-PEP methods
284
+
285
+ # Private methods.
286
+ def _prepare(self):
287
+ external_tables = [
288
+ {'name': name, 'structure': structure, 'data': data}
289
+ for name, (structure, data) in self._external_tables.items()
290
+ ] or None
291
+
292
+ execute = self._client.execute
293
+
294
+ if self._stream_results:
295
+ execute = self._client.execute_iter
296
+ self._settings = self._settings or {}
297
+ self._settings['max_block_size'] = self._max_row_buffer
298
+
299
+ execute_kwargs = {
300
+ 'settings': self._settings,
301
+ 'external_tables': external_tables,
302
+ 'types_check': self._types_check,
303
+ 'query_id': self._query_id
304
+ }
305
+
306
+ return execute, execute_kwargs
307
+
308
+ def _process_response(self, response, executemany=False):
309
+ if executemany:
310
+ self._rowcount = response
311
+ response = None
312
+
313
+ if not response or isinstance(response, int):
314
+ self._columns = self._types = self._rows = []
315
+ if isinstance(response, int):
316
+ self._rowcount = response
317
+ return
318
+
319
+ if self._stream_results:
320
+ columns_with_types = next(response)
321
+ rows = response
322
+
323
+ else:
324
+ rows, columns_with_types = response
325
+
326
+ self._columns_with_types = columns_with_types
327
+
328
+ # Only SELECT queries have columns_with_types.
329
+ # DDL and INSERT INTO ... SELECT queries have empty columns header.
330
+ # We need to obtain rows count only during non-streaming SELECTs.
331
+ if columns_with_types:
332
+ self._columns, self._types = zip(*columns_with_types)
333
+ if not self._stream_results:
334
+ self._rowcount = len(rows)
335
+ else:
336
+ self._columns = self._types = []
337
+
338
+ self._rows = rows
339
+
340
+ def _reset_state(self):
341
+ """
342
+ Resets query state and get ready for another query.
343
+ """
344
+ self._state = self._states.NONE
345
+
346
+ self._columns = None
347
+ self._types = None
348
+ self._rows = None
349
+ self._rowcount = -1
350
+
351
+ self._stream_results = False
352
+ self._max_row_buffer = 0
353
+ self._settings = None
354
+ self._query_id = None
355
+ self._external_tables = {}
356
+ self._types_check = False
357
+
358
+ def _begin_query(self):
359
+ self._state = self._states.RUNNING
360
+
361
+ def _end_query(self):
362
+ self._state = self._states.FINISHED
363
+
364
+ def _check_cursor_closed(self):
365
+ if self._state == self._states.CURSOR_CLOSED:
366
+ raise InterfaceError('cursor already closed')
367
+
368
+ def _check_query_started(self):
369
+ if self._state == self._states.NONE:
370
+ raise ProgrammingError('no results to fetch')
@@ -0,0 +1,40 @@
1
+
2
+
3
+ class Warning(Exception):
4
+ pass
5
+
6
+
7
+ class Error(Exception):
8
+ pass
9
+
10
+
11
+ class InterfaceError(Error):
12
+ pass
13
+
14
+
15
+ class DatabaseError(Error):
16
+ pass
17
+
18
+
19
+ class InternalError(DatabaseError):
20
+ pass
21
+
22
+
23
+ class OperationalError(DatabaseError):
24
+ pass
25
+
26
+
27
+ class ProgrammingError(DatabaseError):
28
+ pass
29
+
30
+
31
+ class IntegrityError(DatabaseError):
32
+ pass
33
+
34
+
35
+ class DataError(DatabaseError):
36
+ pass
37
+
38
+
39
+ class NotSupportedError(DatabaseError):
40
+ pass
@@ -0,0 +1,73 @@
1
+ import re
2
+ from collections import namedtuple
3
+ from functools import lru_cache
4
+
5
+ from .cursor import Cursor
6
+
7
+
8
+ class DictCursor(Cursor):
9
+ """
10
+ A cursor that generates results as :class:`dict`.
11
+
12
+ ``fetch*()`` methods will return dicts instead of tuples.
13
+ """
14
+
15
+ def fetchone(self):
16
+ rv = super(DictCursor, self).fetchone()
17
+ if rv is not None:
18
+ rv = dict(zip(self._columns, rv))
19
+ return rv
20
+
21
+ def fetchmany(self, size=None):
22
+ rv = super(DictCursor, self).fetchmany(size=size)
23
+ return [dict(zip(self._columns, x)) for x in rv]
24
+
25
+ def fetchall(self):
26
+ rv = super(DictCursor, self).fetchall()
27
+ return [dict(zip(self._columns, x)) for x in rv]
28
+
29
+
30
+ class NamedTupleCursor(Cursor):
31
+ """
32
+ A cursor that generates results as named tuples created by
33
+ :func:`~collections.namedtuple`.
34
+
35
+ ``fetch*()`` methods will return named tuples instead of regular tuples, so
36
+ their elements can be accessed both as regular numeric items as well as
37
+ attributes.
38
+ """
39
+
40
+ # ascii except alnum and underscore
41
+ _re_clean = re.compile(
42
+ '[' + re.escape(' !"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~') + ']')
43
+
44
+ @classmethod
45
+ @lru_cache(512)
46
+ def _make_nt(self, key):
47
+ fields = []
48
+ for s in key:
49
+ s = self._re_clean.sub('_', s)
50
+ # Python identifier cannot start with numbers, namedtuple fields
51
+ # cannot start with underscore.
52
+ if s[0] == '_' or '0' <= s[0] <= '9':
53
+ s = 'f' + s
54
+ fields.append(s)
55
+
56
+ return namedtuple('Record', fields)
57
+
58
+ def fetchone(self):
59
+ rv = super(NamedTupleCursor, self).fetchone()
60
+ if rv is not None:
61
+ nt = self._make_nt(self._columns)
62
+ rv = nt(*rv)
63
+ return rv
64
+
65
+ def fetchmany(self, size=None):
66
+ rv = super(NamedTupleCursor, self).fetchmany(size=size)
67
+ nt = self._make_nt(self._columns)
68
+ return [nt(*x) for x in rv]
69
+
70
+ def fetchall(self):
71
+ rv = super(NamedTupleCursor, self).fetchall()
72
+ nt = self._make_nt(self._columns)
73
+ return [nt(*x) for x in rv]