From 1c7718cb8bb74085dcf2eb576b465d5b7bbdd193 Mon Sep 17 00:00:00 2001 From: "artem.morozov" Date: Mon, 3 Feb 2020 14:30:32 +0300 Subject: [PATCH 1/9] added dbapi2 errors and extended Cursor and Connection functionality --- tarantool/dbapi.py | 104 +++++++++++++++++++++++++++++++++------------ tarantool/error.py | 24 +++++++++++ 2 files changed, 101 insertions(+), 27 deletions(-) diff --git a/tarantool/dbapi.py b/tarantool/dbapi.py index 055f0db9..6da1bac3 100644 --- a/tarantool/dbapi.py +++ b/tarantool/dbapi.py @@ -1,55 +1,105 @@ -# -*- coding: utf-8 -*- +""" +Supports python 3.6 and above +""" +from .connection import Connection as BaseConnection +from . import error -class Cursor(object): +class Cursor: + _lastrowid = 0 description = None - rowcount = None arraysize = None + rows = [] + position = 0 + autocommit = True - def __init__(self): - pass + def __init__(self, connection): + self._c = connection def callproc(self, procname, *params): pass - + def close(self): - pass - - def execute(self, query, params): - pass - + self._c.close() + + def execute(self, query, params=None): + if params: + query = query % tuple("'%s'" % param for param in params) + + response = self._c.execute(query) + + if len(response.body) > 1: + self.rows = tuple(response.body.values())[1] + return response + + def lastrowid(self): + return self._lastrowid + + @property + def rowcount(self): + return len(self._c.rows) + def executemany(self, query, params): - pass - + return self.execute(query, params) + def fetchone(self): pass - def fetchmany(self): - pass - + def fetchmany(self, size): + self._lastrowid += size + items = self.rows.copy() + self.rows = [] + return items + def fetchall(self): pass def setinputsizes(self, sizes): pass - + def setoutputsize(self, size, column=None): pass -class Connection(object): - def __init__(self): - pass - - def close(self): - pass - +class Connection(BaseConnection): + rows = [] + _cursor = None + + # DBAPI Extension: supply exceptions as attributes on the connection + Warning = Warning + Error = error.Error + InterfaceError = error.InterfaceError + DataError = error.DataError + DatabaseError = error.DatabaseError + OperationalError = error.OperationalError + IntegrityError = error.IntegrityError + InternalError = error.InternalError + ProgrammingError = error.ProgrammingError + NotSupportedError = error.NotSupportedError + ImproperlyConfigured = Exception + + server_version = 1 + def commit(self): pass + def close(self): + ''' + Close connection to the server + ''' + if self._socket: + self._socket.close() + self._socket = None + def rollback(self): pass - - def cursor(self): - pass \ No newline at end of file + + @classmethod + def cache(cls, cursor): + cls._cursor = cursor + + def cursor(self, params=None): + if not self._cursor: + self._cursor = Cursor(self) + return self._cursor diff --git a/tarantool/error.py b/tarantool/error.py index f49ba60c..d3ea3070 100644 --- a/tarantool/error.py +++ b/tarantool/error.py @@ -43,6 +43,30 @@ class InterfaceError(Error): ''' +class InternalError(DatabaseError): + pass + + +class OperationalError(DatabaseError): + pass + + +class ProgrammingError(DatabaseError): + pass + + +class IntegrityError(DatabaseError): + pass + + +class DataError(DatabaseError): + pass + + +class NotSupportedError(DatabaseError): + pass + + # Monkey patch os.strerror for win32 if sys.platform == "win32": # Windows Sockets Error Codes (not all, but related on network errors) From 713637343dafdaf34907ad77a21839b733350f42 Mon Sep 17 00:00:00 2001 From: "artem.morozov" Date: Tue, 4 Feb 2020 13:37:33 +0300 Subject: [PATCH 2/9] disable quoting for boolean query params --- tarantool/dbapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tarantool/dbapi.py b/tarantool/dbapi.py index 6da1bac3..9b3c1ad4 100644 --- a/tarantool/dbapi.py +++ b/tarantool/dbapi.py @@ -25,7 +25,7 @@ def close(self): def execute(self, query, params=None): if params: - query = query % tuple("'%s'" % param for param in params) + query = query % tuple(str(param) if isinstance(param, bool) else "'%s'" % param for param in params) response = self._c.execute(query) From 6a2851b4b818342ab871205be6ddd565b57f123c Mon Sep 17 00:00:00 2001 From: "artem.morozov" Date: Fri, 7 Feb 2020 23:13:25 +0300 Subject: [PATCH 3/9] updated connection and response --- tarantool/connection.py | 10 +++++----- tarantool/dbapi.py | 17 +++++++++++++++-- tarantool/request.py | 4 ++-- tarantool/response.py | 8 ++++---- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/tarantool/connection.py b/tarantool/connection.py index ee133487..c30f66de 100644 --- a/tarantool/connection.py +++ b/tarantool/connection.py @@ -11,6 +11,7 @@ import ctypes import ctypes.util + try: from ctypes import c_ssize_t except ImportError: @@ -293,13 +294,12 @@ def check(): # Check that connection is alive retbytes = self._sys_recv(sock_fd, buf, 1, flag) err = 0 - if os.name!= 'nt': + if os.name != 'nt': err = ctypes.get_errno() else: err = ctypes.get_last_error() self._socket.setblocking(True) - WWSAEWOULDBLOCK = 10035 if (retbytes < 0) and (err == errno.EAGAIN or err == errno.EWOULDBLOCK or @@ -791,10 +791,10 @@ def execute(self, query, params=None): ''' Execute SQL request. Execute SQL query in database. - - :param query: SQL syntax query + + :param query: SQL syntax query :type query: str - + :param params: Bind values to use in query :type params: list, dict diff --git a/tarantool/dbapi.py b/tarantool/dbapi.py index 9b3c1ad4..e846ada2 100644 --- a/tarantool/dbapi.py +++ b/tarantool/dbapi.py @@ -1,6 +1,9 @@ """ Supports python 3.6 and above """ +from copy import deepcopy + +from tarantool.error import InterfaceError from .connection import Connection as BaseConnection from . import error @@ -13,6 +16,7 @@ class Cursor: rows = [] position = 0 autocommit = True + _rowcount = 0 def __init__(self, connection): self._c = connection @@ -31,6 +35,15 @@ def execute(self, query, params=None): if len(response.body) > 1: self.rows = tuple(response.body.values())[1] + else: + self.rows = [] + if 'UPDATE' not in query or 'INSERT' not in query: + try: + self._rowcount = response.rowcount + except InterfaceError: + pass + else: + self._rowcount = 1 return response def lastrowid(self): @@ -38,7 +51,7 @@ def lastrowid(self): @property def rowcount(self): - return len(self._c.rows) + return self._rowcount def executemany(self, query, params): return self.execute(query, params) @@ -48,7 +61,7 @@ def fetchone(self): def fetchmany(self, size): self._lastrowid += size - items = self.rows.copy() + items = deepcopy(self.rows) self.rows = [] return items diff --git a/tarantool/request.py b/tarantool/request.py index 609223c8..35daa761 100644 --- a/tarantool/request.py +++ b/tarantool/request.py @@ -7,7 +7,6 @@ import msgpack import hashlib - from tarantool.const import ( IPROTO_CODE, IPROTO_SYNC, @@ -50,6 +49,7 @@ binary_types ) + class Request(object): ''' Represents a single request to the server in compliance with the @@ -137,7 +137,7 @@ def sha1(values): request_body = msgpack.dumps({IPROTO_USER_NAME: user, IPROTO_TUPLE: ("chap-sha1", scramble)}) self._body = request_body - + def header(self, length): self._sync = self.conn.generate_sync() # Set IPROTO_SCHEMA_ID: 0 to avoid SchemaReloadException diff --git a/tarantool/response.py b/tarantool/response.py index 7b8a5636..e27dd354 100644 --- a/tarantool/response.py +++ b/tarantool/response.py @@ -35,7 +35,7 @@ class Response(Sequence): and parses binary packet received from the server. ''' - def __init__(self, conn, response): + def __init__(self, conn, response, use_list=False): ''' Create an instance of `Response` using data received from the server. @@ -54,11 +54,11 @@ def __init__(self, conn, response): # Get rid of the following warning. # > PendingDeprecationWarning: encoding is deprecated, # > Use raw=False instead. - unpacker = msgpack.Unpacker(use_list=True, raw=False) + unpacker = msgpack.Unpacker(use_list=use_list, raw=False) elif conn.encoding is not None: - unpacker = msgpack.Unpacker(use_list=True, encoding=conn.encoding) + unpacker = msgpack.Unpacker(use_list=use_list, encoding=conn.encoding) else: - unpacker = msgpack.Unpacker(use_list=True) + unpacker = msgpack.Unpacker(use_list=use_list) unpacker.feed(response) header = unpacker.unpack() From 3776c0dcf32542b1a8cb3992a380cc77604d7cc0 Mon Sep 17 00:00:00 2001 From: "artem.morozov" Date: Fri, 14 Feb 2020 15:10:26 +0300 Subject: [PATCH 4/9] workaround for update and insert --- tarantool/connection.py | 15 +++++++++++++++ tarantool/dbapi.py | 31 +++++++++++++++---------------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/tarantool/connection.py b/tarantool/connection.py index c30f66de..ee6e8826 100644 --- a/tarantool/connection.py +++ b/tarantool/connection.py @@ -56,6 +56,12 @@ InterfaceError, SchemaError, NetworkWarning, + OperationalError, + DataError, + IntegrityError, + InternalError, + ProgrammingError, + NotSupportedError, SchemaReloadException, warn ) @@ -78,11 +84,20 @@ class Connection(object): Also this class provides low-level interface to data manipulation (insert/delete/update/select). ''' + # DBAPI Extension: supply exceptions as attributes on the connection Error = tarantool.error DatabaseError = DatabaseError InterfaceError = InterfaceError SchemaError = SchemaError NetworkError = NetworkError + Warning = Warning + DataError = DataError + OperationalError = OperationalError + IntegrityError = IntegrityError + InternalError = InternalError + ProgrammingError = ProgrammingError + NotSupportedError = NotSupportedError + ImproperlyConfigured = Exception def __init__(self, host, port, user=None, diff --git a/tarantool/dbapi.py b/tarantool/dbapi.py index e846ada2..58742e32 100644 --- a/tarantool/dbapi.py +++ b/tarantool/dbapi.py @@ -1,12 +1,12 @@ """ Supports python 3.6 and above """ +import re from copy import deepcopy from tarantool.error import InterfaceError from .connection import Connection as BaseConnection -from . import error class Cursor: @@ -28,16 +28,27 @@ def close(self): self._c.close() def execute(self, query, params=None): + def convert_param(p): + print('PARAM: ', p) + if isinstance(p, bool): + return str(p) + elif p is None: + return "NULL" + return "'%s'" % p + if params: - query = query % tuple(str(param) if isinstance(param, bool) else "'%s'" % param for param in params) + query = query % tuple(convert_param(param) for param in params) + print(query) response = self._c.execute(query) if len(response.body) > 1: self.rows = tuple(response.body.values())[1] else: self.rows = [] - if 'UPDATE' not in query or 'INSERT' not in query: + + rc_pattern = re.compile(r'^(UPDATE|INSERT)') + if rc_pattern.match(query): try: self._rowcount = response.rowcount except InterfaceError: @@ -46,6 +57,7 @@ def execute(self, query, params=None): self._rowcount = 1 return response + @property def lastrowid(self): return self._lastrowid @@ -79,19 +91,6 @@ class Connection(BaseConnection): rows = [] _cursor = None - # DBAPI Extension: supply exceptions as attributes on the connection - Warning = Warning - Error = error.Error - InterfaceError = error.InterfaceError - DataError = error.DataError - DatabaseError = error.DatabaseError - OperationalError = error.OperationalError - IntegrityError = error.IntegrityError - InternalError = error.InternalError - ProgrammingError = error.ProgrammingError - NotSupportedError = error.NotSupportedError - ImproperlyConfigured = Exception - server_version = 1 def commit(self): From 4f6b408c100aaec8e529ba8f0a33f1241ce357bb Mon Sep 17 00:00:00 2001 From: "artem.morozov" Date: Sat, 15 Feb 2020 15:58:23 +0300 Subject: [PATCH 5/9] workaround for lastrowid --- tarantool/dbapi.py | 36 +++++++++++++++++++++--------------- tarantool/response.py | 4 +++- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/tarantool/dbapi.py b/tarantool/dbapi.py index 58742e32..022beb51 100644 --- a/tarantool/dbapi.py +++ b/tarantool/dbapi.py @@ -27,25 +27,21 @@ def callproc(self, procname, *params): def close(self): self._c.close() - def execute(self, query, params=None): - def convert_param(p): - print('PARAM: ', p) - if isinstance(p, bool): - return str(p) - elif p is None: - return "NULL" - return "'%s'" % p + def _convert_param(self, p): + if isinstance(p, bool): + return str(p) + elif p is None: + return "NULL" + return "'%s'" % p + def execute(self, query, params=None): if params: - query = query % tuple(convert_param(param) for param in params) + query = query % tuple(self._convert_param(param) for param in params) - print(query) + # print(query) response = self._c.execute(query) - if len(response.body) > 1: - self.rows = tuple(response.body.values())[1] - else: - self.rows = [] + self.rows = tuple(response.body.values())[1] if len(response.body) > 1 else [] rc_pattern = re.compile(r'^(UPDATE|INSERT)') if rc_pattern.match(query): @@ -55,6 +51,17 @@ def convert_param(p): pass else: self._rowcount = 1 + + def extract_last_row_id(body): # Need to be checked + try: + val = tuple(tuple(body.items())[0][-1].items())[-1][-1][0] + except TypeError: + val = 1 + return val + + u_pattern = re.compile(r'^INSERT') + if u_pattern.match(query): + self._lastrowid = extract_last_row_id(response.body) return response @property @@ -72,7 +79,6 @@ def fetchone(self): pass def fetchmany(self, size): - self._lastrowid += size items = deepcopy(self.rows) self.rows = [] return items diff --git a/tarantool/response.py b/tarantool/response.py index e27dd354..11cfd8c1 100644 --- a/tarantool/response.py +++ b/tarantool/response.py @@ -244,4 +244,6 @@ def __str__(self): output.pop() return ''.join(output) - __repr__ = __str__ + def __repr__(self): + return self.__str__() + From 0332c537ce696831bcff2367ffe0daaff598ea9b Mon Sep 17 00:00:00 2001 From: "artem.morozov" Date: Sat, 22 Feb 2020 01:47:05 +0300 Subject: [PATCH 6/9] implemented fetchone logic --- tarantool/dbapi.py | 85 +++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/tarantool/dbapi.py b/tarantool/dbapi.py index 022beb51..6a5c40d4 100644 --- a/tarantool/dbapi.py +++ b/tarantool/dbapi.py @@ -2,9 +2,14 @@ Supports python 3.6 and above """ import re +from collections import deque from copy import deepcopy +from itertools import islice -from tarantool.error import InterfaceError +from pycallgraph import PyCallGraph +from pycallgraph.output import GraphvizOutput + +from tarantool.error import InterfaceError, ProgrammingError from .connection import Connection as BaseConnection @@ -12,28 +17,39 @@ class Cursor: _lastrowid = 0 description = None - arraysize = None - rows = [] position = 0 + arraysize = 100 autocommit = True _rowcount = 0 + ui_pattern = re.compile(r'^(UPDATE|INSERT)') + u_pattern = re.compile(r'^INSERT') def __init__(self, connection): self._c = connection + self.rows = [] def callproc(self, procname, *params): pass - def close(self): - self._c.close() + def close(self): # TODO: Find out how to implement closing connection correctly + pass - def _convert_param(self, p): + @staticmethod + def _convert_param(p): + if p is None: + return "NULL" if isinstance(p, bool): return str(p) - elif p is None: - return "NULL" return "'%s'" % p + @staticmethod + def extract_last_row_id(body): # Need to be checked + try: + val = tuple(tuple(body.items())[0][-1].items())[-1][-1][0] + except TypeError: + val = 1 + return val + def execute(self, query, params=None): if params: query = query % tuple(self._convert_param(param) for param in params) @@ -43,25 +59,16 @@ def execute(self, query, params=None): self.rows = tuple(response.body.values())[1] if len(response.body) > 1 else [] - rc_pattern = re.compile(r'^(UPDATE|INSERT)') - if rc_pattern.match(query): + if self.ui_pattern.match(query): try: self._rowcount = response.rowcount except InterfaceError: - pass + self._rowcount = 1 else: self._rowcount = 1 - def extract_last_row_id(body): # Need to be checked - try: - val = tuple(tuple(body.items())[0][-1].items())[-1][-1][0] - except TypeError: - val = 1 - return val - - u_pattern = re.compile(r'^INSERT') - if u_pattern.match(query): - self._lastrowid = extract_last_row_id(response.body) + if self.u_pattern.match(query): + self._lastrowid = self.extract_last_row_id(response.body) return response @property @@ -76,16 +83,22 @@ def executemany(self, query, params): return self.execute(query, params) def fetchone(self): - pass + return self.rows[0] if len(self.rows) else None def fetchmany(self, size): + if len(self.rows) < size: + items = self.rows + self.rows = [] + else: + items, self.rows = self.rows[:size], self.rows[size:] + + return items if len(items) else [] + + def fetchall(self): items = deepcopy(self.rows) self.rows = [] return items - def fetchall(self): - pass - def setinputsizes(self, sizes): pass @@ -94,30 +107,24 @@ def setoutputsize(self, size, column=None): class Connection(BaseConnection): - rows = [] _cursor = None - server_version = 1 + server_version = 2 def commit(self): pass + def rollback(self): + pass + def close(self): - ''' - Close connection to the server - ''' if self._socket: self._socket.close() self._socket = None - def rollback(self): - pass - - @classmethod - def cache(cls, cursor): - cls._cursor = cursor + def _set_cursor(self): + self._cursor = Cursor(self) + return self._cursor def cursor(self, params=None): - if not self._cursor: - self._cursor = Cursor(self) - return self._cursor + return self._cursor or self._set_cursor() From 5d93a803b900ecf9ebbf84804df014c821a4e34d Mon Sep 17 00:00:00 2001 From: "artem.morozov" Date: Tue, 3 Mar 2020 15:22:53 +0300 Subject: [PATCH 7/9] minor changes --- tarantool/dbapi.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/tarantool/dbapi.py b/tarantool/dbapi.py index 6a5c40d4..bd8fe906 100644 --- a/tarantool/dbapi.py +++ b/tarantool/dbapi.py @@ -2,25 +2,20 @@ Supports python 3.6 and above """ import re -from collections import deque from copy import deepcopy -from itertools import islice -from pycallgraph import PyCallGraph -from pycallgraph.output import GraphvizOutput - -from tarantool.error import InterfaceError, ProgrammingError +from tarantool.error import InterfaceError from .connection import Connection as BaseConnection class Cursor: _lastrowid = 0 + _rowcount = 0 description = None position = 0 - arraysize = 100 + arraysize = 200 autocommit = True - _rowcount = 0 ui_pattern = re.compile(r'^(UPDATE|INSERT)') u_pattern = re.compile(r'^INSERT') @@ -43,18 +38,17 @@ def _convert_param(p): return "'%s'" % p @staticmethod - def extract_last_row_id(body): # Need to be checked + def _extract_last_row_id(body): # Need to be checked try: val = tuple(tuple(body.items())[0][-1].items())[-1][-1][0] except TypeError: - val = 1 + val = -1 return val def execute(self, query, params=None): if params: query = query % tuple(self._convert_param(param) for param in params) - # print(query) response = self._c.execute(query) self.rows = tuple(response.body.values())[1] if len(response.body) > 1 else [] @@ -68,9 +62,12 @@ def execute(self, query, params=None): self._rowcount = 1 if self.u_pattern.match(query): - self._lastrowid = self.extract_last_row_id(response.body) + self._lastrowid = self._extract_last_row_id(response.body) return response + def executemany(self, query, params): + return self.execute(query, params) + @property def lastrowid(self): return self._lastrowid @@ -79,9 +76,6 @@ def lastrowid(self): def rowcount(self): return self._rowcount - def executemany(self, query, params): - return self.execute(query, params) - def fetchone(self): return self.rows[0] if len(self.rows) else None From 021cb1772b3e2c11aca27625c6b094a2c26aded0 Mon Sep 17 00:00:00 2001 From: "artem.morozov" Date: Fri, 17 Jul 2020 12:38:30 +0300 Subject: [PATCH 8/9] minor changes and added dbapi2 descriptions --- tarantool/dbapi.py | 184 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 164 insertions(+), 20 deletions(-) diff --git a/tarantool/dbapi.py b/tarantool/dbapi.py index bd8fe906..e5845de0 100644 --- a/tarantool/dbapi.py +++ b/tarantool/dbapi.py @@ -1,13 +1,13 @@ """ -Supports python 3.6 and above +https://www.python.org/dev/peps/pep-0249/ """ import re -from copy import deepcopy from tarantool.error import InterfaceError - from .connection import Connection as BaseConnection +update_insert_pattern = re.compile(r'^UPDATE|INSERT') + class Cursor: _lastrowid = 0 @@ -16,18 +16,31 @@ class Cursor: position = 0 arraysize = 200 autocommit = True - ui_pattern = re.compile(r'^(UPDATE|INSERT)') - u_pattern = re.compile(r'^INSERT') def __init__(self, connection): self._c = connection self.rows = [] - def callproc(self, procname, *params): - pass + def callproc(self, procname, *params): # TODO + """ + Call a stored database procedure with the given name. The sequence of + parameters must contain one entry for each argument that the + procedure expects. The result of the call is returned as modified + copy of the input sequence. Input parameters are left untouched, + output and input/output parameters replaced with possibly new values. - def close(self): # TODO: Find out how to implement closing connection correctly - pass + The procedure may also provide a result set as output. This must then + be made available through the standard .fetch*() methods. + """ + + def close(self): # TODO + """ + Close the cursor now (rather than whenever __del__ is called). + + The cursor will be unusable from this point forward; an Error (or + subclass) exception will be raised if any operation is attempted with + the cursor. + """ @staticmethod def _convert_param(p): @@ -38,7 +51,7 @@ def _convert_param(p): return "'%s'" % p @staticmethod - def _extract_last_row_id(body): # Need to be checked + def _extract_last_row_id(body): # TODO: Need to be checked try: val = tuple(tuple(body.items())[0][-1].items())[-1][-1][0] except TypeError: @@ -46,14 +59,42 @@ def _extract_last_row_id(body): # Need to be checked return val def execute(self, query, params=None): + """ + Prepare and execute a database operation (query or command). + + Parameters may be provided as sequence or mapping and will be bound + to variables in the operation. Variables are specified in a + database-specific notation (see the module's paramstyle attribute for + details). + + A reference to the operation will be retained by the cursor. If the + same operation object is passed in again, then the cursor can + optimize its behavior. This is most effective for algorithms where + the same operation is used, but different parameters are bound to it + (many times). + + For maximum efficiency when reusing an operation, it is best to use + the .setinputsizes() method to specify the parameter types and sizes + ahead of time. It is legal for a parameter to not match the + predefined information; the implementation should compensate, + possibly with a loss of efficiency. + + The parameters may also be specified as list of tuples to e.g. insert + multiple rows in a single operation, but this kind of usage is + deprecated: .executemany() should be used instead. + + Return values are not defined. + """ if params: - query = query % tuple(self._convert_param(param) for param in params) + query = query % tuple( + self._convert_param(param) for param in params) response = self._c.execute(query) - self.rows = tuple(response.body.values())[1] if len(response.body) > 1 else [] + self.rows = tuple(response.body.values())[1] if len( + response.body) > 1 else [] - if self.ui_pattern.match(query): + if update_insert_pattern.match(query.upper()): try: self._rowcount = response.rowcount except InterfaceError: @@ -61,7 +102,7 @@ def execute(self, query, params=None): else: self._rowcount = 1 - if self.u_pattern.match(query): + if query.upper().startswith('INSERT'): self._lastrowid = self._extract_last_row_id(response.body) return response @@ -70,16 +111,69 @@ def executemany(self, query, params): @property def lastrowid(self): + """ + This read-only attribute provides the rowid of the last modified row + (most databases return a rowid only when a single INSERT operation is + performed). If the operation does not set a rowid or if the database + does not support rowids, this attribute should be set to None. + + The semantics of .lastrowid are undefined in case the last executed + statement modified more than one row, e.g. when using INSERT with + .executemany(). + + Warning Message: "DB-API extension cursor.lastrowid used" + """ return self._lastrowid @property def rowcount(self): + """ + This read-only attribute specifies the number of rows that the last + .execute*() produced (for DQL statements like SELECT) or affected ( + for DML statements like UPDATE or INSERT). + + The attribute is -1 in case no .execute*() has been performed on the + cursor or the rowcount of the last operation is cannot be determined + by the interface. + + Note: + Future versions of the DB API specification could redefine the latter + case to have the object return None instead of -1. + """ return self._rowcount def fetchone(self): - return self.rows[0] if len(self.rows) else None + """ + Fetch the next row of a query result set, returning a single + sequence, or None when no more data is available. + + An Error (or subclass) exception is raised if the previous call to + .execute*() did not produce any result set or no call was issued yet. + """ + + return self.fetchmany(1)[0] if len(self.rows) else None def fetchmany(self, size): + """ + Fetch the next set of rows of a query result, returning a sequence of + sequences (e.g. a list of tuples). An empty sequence is returned when + no more rows are available. + + The number of rows to fetch per call is specified by the parameter. + If it is not given, the cursor's arraysize determines the number of + rows to be fetched. The method should try to fetch as many rows as + indicated by the size parameter. If this is not possible due to the + specified number of rows not being available, fewer rows may be + returned. + + An Error (or subclass) exception is raised if the previous call to + .execute*() did not produce any result set or no call was issued yet. + + Note there are performance considerations involved with the size + parameter. For optimal performance, it is usually best to use the + .arraysize attribute. If the size parameter is used, then it is best + for it to retain the same value from one .fetchmany() call to the next. + """ if len(self.rows) < size: items = self.rows self.rows = [] @@ -89,12 +183,32 @@ def fetchmany(self, size): return items if len(items) else [] def fetchall(self): - items = deepcopy(self.rows) + """Fetch all (remaining) rows of a query result, returning them as a + sequence of sequences (e.g. a list of tuples). Note that the cursor's + arraysize attribute can affect the performance of this operation. + + An Error (or subclass) exception is raised if the previous call to + .execute*() did not produce any result set or no call was issued yet. + """ + items = self.rows[:] self.rows = [] return items def setinputsizes(self, sizes): - pass + """This can be used before a call to .execute*() to predefine memory + areas for the operation's parameters. + + sizes is specified as a sequence — one item for each input parameter. + The item should be a Type Object that corresponds to the input that + will be used, or it should be an integer specifying the maximum + length of a string parameter. If the item is None, then no predefined + memory area will be reserved for that column (this is useful to avoid + predefined areas for large inputs). + + This method would be used before the .execute*() method is invoked. + + Implementations are free to have this method do nothing and users are + free to not use it.""" def setoutputsize(self, size, column=None): pass @@ -105,13 +219,36 @@ class Connection(BaseConnection): server_version = 2 - def commit(self): - pass + def commit(self): # TODO + """ + Commit any pending transaction to the database. + + Note that if the database supports an auto-commit feature, this must + be initially off. An interface method may be provided to turn it back + on. + + Database modules that do not support transactions should implement + this method with void functionality. + """ def rollback(self): - pass + """ + In case a database does provide transactions this method causes the + database to roll back to the start of any pending transaction. + Closing a connection without committing the changes first will cause + an implicit rollback to be performed. + """ def close(self): + """ + Close the connection now (rather than whenever .__del__() is called). + + The connection will be unusable from this point forward; an Error (or + subclass) exception will be raised if any operation is attempted with + the connection. The same applies to all cursor objects trying to use + the connection. Note that closing a connection without committing the + changes first will cause an implicit rollback to be performed. + """ if self._socket: self._socket.close() self._socket = None @@ -121,4 +258,11 @@ def _set_cursor(self): return self._cursor def cursor(self, params=None): + """ + Return a new Cursor Object using the connection. + + If the database does not provide a direct cursor concept, the module + will have to emulate cursors using other means to the extent needed + by this specification. + """ return self._cursor or self._set_cursor() From e3be7de95703e503699dee94194ae6ca34f7984f Mon Sep 17 00:00:00 2001 From: "artem.morozov" Date: Fri, 17 Jul 2020 13:08:48 +0300 Subject: [PATCH 9/9] set use_list to False for Response object --- tarantool/response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tarantool/response.py b/tarantool/response.py index 11cfd8c1..1a696e85 100644 --- a/tarantool/response.py +++ b/tarantool/response.py @@ -35,7 +35,7 @@ class Response(Sequence): and parses binary packet received from the server. ''' - def __init__(self, conn, response, use_list=False): + def __init__(self, conn, response, use_list=True): ''' Create an instance of `Response` using data received from the server.