Skip to content

Commit 980089a

Browse files
committed
API redesign: enable custom serializers & deserializers (relates to #35)
1 parent b32911b commit 980089a

File tree

8 files changed

+77
-35
lines changed

8 files changed

+77
-35
lines changed

CHANGELOG.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
------------------------------------------------------------------------------
2+
qPython 1.2.0 [2016.01.*]
3+
------------------------------------------------------------------------------
4+
5+
- API redesign: enable custom serializers & deserializers
6+
17
------------------------------------------------------------------------------
28
qPython 1.1.0 [2015.11.25]
39
------------------------------------------------------------------------------

doc/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def __getattr__(cls, name):
6464

6565
# General information about the project.
6666
project = u'qPython'
67-
copyright = u'2014-2015, DEVnet'
67+
copyright = u'2014-2016, DEVnet'
6868

6969
# The version info for the project you're documenting, acts as replacement for
7070
# |version| and |release|, also used in various other places throughout the

doc/source/connection.rst

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ qPython wraps connection to a q process in instances of the
1313
q.close()
1414

1515
.. note:: the connection is not established when the connector instance is
16-
created. The connection is initialized explicitly by calling the
16+
created. The connection is initialised explicitly by calling the
1717
:meth:`~qpython.qconnection.QConnection.open` method.
1818

1919

@@ -48,4 +48,44 @@ to numpy `datetime64`/`timedelta64` representation.
4848
Conversion options can be also overwritten while executing
4949
synchronous/asynchronous queries (:meth:`~qpython.qconnection.QConnection.sync`,
5050
:meth:`~qpython.qconnection.QConnection.async`) or retrieving data from q
51-
(:meth:`~qpython.qconnection.QConnection.receive`).
51+
(:meth:`~qpython.qconnection.QConnection.receive`).
52+
53+
54+
Custom IPC protocol serializers/deserializers
55+
*********************************************
56+
57+
Default IPC serializers (`.QWriter` and `.PandasQWriter`) and deserializers
58+
(`.QReader` and `.PandasQReader`) can be replaced with custom implementations.
59+
This allow users to override the default mapping between the q types and Python
60+
representation.
61+
::
62+
63+
q = qconnection.QConnection(host = 'localhost', port = 5000, writer_class = MyQWriter, reader_class = MyQReader)
64+
65+
66+
::
67+
68+
class MyQReader(QReader):
69+
# QReader and QWriter use decorators to map data types and corresponding function handlers
70+
parse = Mapper(QReader._reader_map)
71+
72+
def _read_list(self, qtype):
73+
if qtype == QSYMBOL_LIST:
74+
self._buffer.skip()
75+
length = self._buffer.get_int()
76+
symbols = self._buffer.get_symbols(length)
77+
return [s.decode(self._encoding) for s in symbols]
78+
else:
79+
return QReader._read_list(self, qtype = qtype)
80+
81+
@parse(QSYMBOL)
82+
def _read_symbol(self, qtype = QSYMBOL):
83+
return numpy.string_(self._buffer.get_symbol()).decode(self._encoding)
84+
85+
86+
with qconnection.QConnection(host='localhost', port=5000, reader_class = MyQReader) as q:
87+
symbols = q.sync('`foo`bar')
88+
print(symbols, type(symbols), type(symbols[0]))
89+
90+
symbol = q.sync('`foo')
91+
print(symbol, type(symbol))

qpython/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
__all__ = ['qconnection', 'qtype', 'qtemporal', 'qcollection']
1818

1919

20-
__version__ = '1.1.0'
20+
__version__ = '1.2.0b1'
2121

2222

2323

qpython/qconnection.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ class QConnection(object):
6262
- `username` (`string` or `None`) - username for q authentication/authorization
6363
- `password` (`string` or `None`) - password for q authentication/authorization
6464
- `timeout` (`nonnegative float` or `None`) - set a timeout on blocking socket operations
65+
- `encoding` (`string`) - string encoding for data deserialization
66+
- `reader_class` (subclass of `QReader`) - data deserializer
67+
- `writer_class` (subclass of `QWriter`) - data serializer
6568
:Options:
6669
- `raw` (`boolean`) - if ``True`` returns raw data chunk instead of parsed
6770
data, **Default**: ``False``
@@ -74,7 +77,8 @@ class QConnection(object):
7477
strings are encoded as q strings instead of chars, **Default**: ``False``
7578
'''
7679

77-
def __init__(self, host, port, username = None, password = None, timeout = None, encoding = 'latin-1', **options):
80+
81+
def __init__(self, host, port, username = None, password = None, timeout = None, encoding = 'latin-1', reader_class = None, writer_class = None, **options):
7882
self.host = host
7983
self.port = port
8084
self.username = username
@@ -89,6 +93,20 @@ def __init__(self, host, port, username = None, password = None, timeout = None,
8993

9094
self._options = MetaData(**CONVERSION_OPTIONS.union_dict(**options))
9195

96+
try:
97+
from qpython._pandas import PandasQReader, PandasQWriter
98+
self._reader_class = PandasQReader
99+
self._writer_class = PandasQWriter
100+
except ImportError:
101+
self._reader_class = QReader
102+
self._writer_class = QWriter
103+
104+
if reader_class:
105+
self._reader_class = reader_class
106+
107+
if writer_class:
108+
self._writer_class = writer_class
109+
92110

93111
def __enter__(self):
94112
self.open()
@@ -124,8 +142,8 @@ def open(self):
124142
self._init_socket()
125143
self._initialize()
126144

127-
self._writer = QWriter(self._connection, protocol_version = self._protocol_version)
128-
self._reader = QReader(self._connection.makefile('b'))
145+
self._writer = self._writer_class(self._connection, protocol_version = self._protocol_version)
146+
self._reader = self._reader_class(self._connection.makefile('b'))
129147

130148

131149
def _init_socket(self):

qpython/qreader.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,24 +99,13 @@ class QReader(object):
9999
100100
:Parameters:
101101
- `stream` (`file object` or `None`) - data input stream
102+
- `encoding` (`string`) - encoding for characters parsing
102103
'''
103104

104105
_reader_map = {}
105106
parse = Mapper(_reader_map)
106107

107108

108-
def __new__(cls, *args, **kwargs):
109-
if cls is QReader:
110-
# try to load optional pandas binding
111-
try:
112-
from qpython._pandas import PandasQReader
113-
return super(QReader, cls).__new__(PandasQReader)
114-
except ImportError:
115-
return super(QReader, cls).__new__(QReader)
116-
else:
117-
return super(QReader, cls).__new__(cls)
118-
119-
120109
def __init__(self, stream, encoding = 'latin-1'):
121110
self._stream = stream
122111
self._buffer = QReader.BytesBuffer()
@@ -257,7 +246,7 @@ def _read_symbol(self, qtype = QSYMBOL):
257246

258247
@parse(QCHAR)
259248
def _read_char(self, qtype = QCHAR):
260-
return chr(self._read_atom(QCHAR)).encode(self._encoding)
249+
return chr(self._read_atom(QCHAR)).encode(self._encoding)
261250

262251

263252
@parse(QGUID)

qpython/qwriter.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,6 @@ class QWriter(object):
5050
serialize = Mapper(_writer_map)
5151

5252

53-
def __new__(cls, *args, **kwargs):
54-
if cls is QWriter:
55-
# try to load optional pandas binding
56-
try:
57-
from qpython._pandas import PandasQWriter
58-
return super(QWriter, cls).__new__(PandasQWriter)
59-
except ImportError:
60-
return super(QWriter, cls).__new__(QWriter)
61-
else:
62-
return super(QWriter, cls).__new__(cls)
63-
64-
6553
def __init__(self, stream, protocol_version):
6654
self._stream = stream
6755
self._protocol_version = protocol_version

tests/pandas_test.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
from io import BytesIO
2424

2525
from collections import OrderedDict
26-
from qpython import qreader, MetaData, qwriter
26+
from qpython import MetaData
27+
from qpython._pandas import PandasQReader, PandasQWriter
2728
from qpython.qtype import * # @UnusedWildImport
2829
from qpython.qcollection import qlist, QList, QTemporalList, QDictionary
2930
from qpython.qtemporal import QTemporal
@@ -250,7 +251,7 @@ def test_reading_pandas():
250251
sys.stdout.write(' %-75s' % query)
251252
try:
252253
buffer_.seek(0)
253-
stream_reader = qreader.QReader(buffer_)
254+
stream_reader = PandasQReader(buffer_)
254255
result = stream_reader.read(pandas = True).data
255256
if isinstance(value, dict):
256257
if 'index' in value:
@@ -271,7 +272,7 @@ def test_reading_pandas():
271272

272273

273274
def test_writing_pandas():
274-
w = qwriter.QWriter(None, 3)
275+
w = PandasQWriter(None, 3)
275276

276277
for query, value in iter(PANDAS_EXPRESSIONS.items()):
277278
sys.stdout.write( '%-75s' % query )

0 commit comments

Comments
 (0)