Skip to content

Commit f4face9

Browse files
committed
Refactoring in Connection and Response
1 parent dcf297a commit f4face9

File tree

2 files changed

+60
-60
lines changed

2 files changed

+60
-60
lines changed

src/tarantool/connection.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
This module provides low-level API for Tarantool
55
'''
66

7+
import ctypes
78
import socket
89
import sys
910
import time
@@ -74,6 +75,38 @@ def connect(self):
7475
raise NetworkError(e)
7576

7677

78+
def _read_response(self):
79+
'''
80+
Read response from the transport (socket)
81+
82+
:return: tuple of the form (header, body)
83+
:rtype: tuple of two byte arrays
84+
'''
85+
# Read response header
86+
buff_header = ctypes.create_string_buffer(12)
87+
nbytes = self._socket.recv_into(buff_header, 12)
88+
89+
# Immediately raises an exception if the data cannot be read
90+
if nbytes != 12:
91+
raise socket.error(socket.errno.ECONNABORTED, "Software caused connection abort")
92+
93+
# Extract body lenght from header
94+
body_length = struct_L.unpack(buff_header[4:8])[0]
95+
96+
# Unpack body if it is not empty (i.e. not PING)
97+
if body_length != 0:
98+
buff_body = ctypes.create_string_buffer(body_length)
99+
nbytes = self._socket.recv_into(buff_body)
100+
# Immediately raises an exception if the data cannot be read
101+
if nbytes != body_length:
102+
raise socket.error(socket.errno.ECONNABORTED, "Software caused connection abort")
103+
else:
104+
buff_body = b""
105+
106+
return buff_header, buff_body
107+
108+
109+
77110
def _send_request_wo_reconnect(self, request, field_types=None):
78111
'''\
79112
:rtype: `Response` instance
@@ -86,7 +119,8 @@ def _send_request_wo_reconnect(self, request, field_types=None):
86119
for attempt in xrange(RETRY_MAX_ATTEMPTS): # pylint: disable=W0612
87120
try:
88121
self._socket.sendall(bytes(request))
89-
response = Response(self._socket, field_types)
122+
header, body = self._read_response()
123+
response = Response(header, body, field_types)
90124
except socket.error as e:
91125
raise NetworkError(e)
92126

src/tarantool/response.py

Lines changed: 25 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,19 @@ class Response(list):
7575
packet received from the server.
7676
'''
7777

78-
def __init__(self, _socket, field_types=None):
78+
def __init__(self, header, body, field_types=None):
7979
'''\
8080
Create an instance of `Response` using data received from the server.
8181
8282
__init__() itself reads data from the socket, parses response body and
8383
sets appropriate instance attributes.
8484
85-
:params _socket: socket connected to the server
86-
:type _socket: instance of socket.socket class (from stdlib)
85+
:param header: header of the response
86+
:type header: array of bytes
87+
:param body: body of the response
88+
:type body: array of bytes
8789
'''
90+
8891
# This is not necessary, because underlying list data structures are created in the __new__(). But let it be.
8992
super(Response, self).__init__()
9093

@@ -97,57 +100,10 @@ def __init__(self, _socket, field_types=None):
97100
self._rowcount = None
98101
self.field_types = field_types
99102

100-
# Read response header
101-
buff = ctypes.create_string_buffer(16)
102-
nbytes = _socket.recv_into(buff, 16, )
103-
104-
# Immediately raises an exception if the data cannot be read
105-
if nbytes != 16:
106-
raise socket.error(socket.errno.ECONNABORTED, "Software caused connection abort")
107-
108-
# Unpack header (including <return_code> attribute)
109-
self._request_type, self._body_length, self._request_id, self._return_code = struct_LLLL.unpack(buff)
110-
111-
# Separate return_code and completion_code
112-
self._completion_status = self._return_code & 0x00ff
113-
self._return_code = self._return_code >> 8
114-
115-
# Unpack body if there is one (i.e. not PING)
116-
if self._body_length != 0:
117-
118-
# In the protocol description <body_length> includes 4 bytes of <return_code>
119-
self._body_length -= 4
120-
121-
# Read response body
122-
buff = ctypes.create_string_buffer(self._body_length)
123-
nbytes = _socket.recv_into(buff)
124-
125-
# Immediately raises an exception if the data cannot be read
126-
if nbytes != self._body_length:
127-
raise socket.error(socket.errno.ECONNABORTED, "Software caused connection abort")
128-
129-
if self._return_code == 0:
130-
# If no errors, unpack response body
131-
self._unpack_body(buff)
132-
else:
133-
# In case of error unpack body as error message
134-
self._unpack_message(buff)
135-
if self._completion_status == 2:
136-
raise DatabaseError(self._return_code, self._return_message)
137-
138-
139-
def _unpack_message(self, buff):
140-
'''\
141-
Extract error message from response body
142-
Called when return_code! = 0.
143-
144-
:param buff: buffer containing request body
145-
:type byff: ctypes buffer
146-
:return: error message
147-
:rtype: str
148-
'''
149-
150-
self._return_message = unicode(buff.value, "utf8", "replace")
103+
# Unpack header
104+
self._request_type, self._body_length, self._request_id = struct_LLL.unpack(header)
105+
if body:
106+
self._unpack_body(body)
151107

152108

153109
@staticmethod
@@ -209,16 +165,26 @@ def _unpack_body(self, buff):
209165
:type byff: ctypes buffer
210166
'''
211167

212-
# Unpack <count> (first 4 bytes) - how many records returned
213-
self._rowcount = struct_L.unpack_from(buff)[0]
168+
# Unpack <return_code> and <count> (how many records affected or selected)
169+
self._return_code, self._rowcount = struct_LL.unpack_from(buff, offset=0)
170+
171+
# Separate return_code and completion_code
172+
self._completion_status = self._return_code & 0x00ff
173+
self._return_code = self._return_code >> 8
174+
175+
# In case of an error unpack the body as an error message
176+
if self._return_code != 0:
177+
self._return_message = unicode(buff.value, "utf8", "replace")
178+
if self._completion_status == 2:
179+
raise DatabaseError(self._return_code, self._return_message)
214180

215-
# If the response body contains only <count> - there is no tuples to unpack
216-
if self._body_length == 4:
181+
# If the response don't contains any tuples - there is no tuples to unpack
182+
if self._body_length == 8:
217183
return
218184

219185
# Parse response tuples (<fq_tuple>)
220186
if self._rowcount > 0:
221-
offset = 4 # The first 4 bytes in the response body is the <count> we have already read
187+
offset = 8 # The first 4 bytes in the response body is the <count> we have already read
222188
while offset < self._body_length:
223189
'''
224190
# In resonse tuples have the form <size><tuple> (<fq_tuple> ::= <size><tuple>).

0 commit comments

Comments
 (0)