PKKDHÐG݃œœprestornado/exc.py""" Package private common utilities. Do not use directly. """ from __future__ import absolute_import from __future__ import unicode_literals __all__ = [ 'Error', 'Warning', 'InterfaceError', 'DatabaseError', 'InternalError', 'OperationalError', 'ProgrammingError', 'DataError', 'NotSupportedError', ] class Error(StandardError): """Exception that is the base class of all other error exceptions. You can use this to catch all errors with one single except statement. """ pass class Warning(StandardError): """Exception raised for important warnings like data truncations while inserting, etc.""" pass class InterfaceError(Error): """Exception raised for errors that are related to the database interface rather than the database itself. """ pass class DatabaseError(Error): """Exception raised for errors that are related to the database.""" pass class InternalError(DatabaseError): """Exception raised when the database encounters an internal error, e.g. the cursor is not valid anymore, the transaction is out of sync, etc.""" pass class OperationalError(DatabaseError): """Exception raised for errors that are related to the database's operation and not necessarily under the control of the programmer, e.g. an unexpected disconnect occurs, the data source name is not found, a transaction could not be processed, a memory allocation error occurred during processing, etc. """ pass class ProgrammingError(DatabaseError): """Exception raised for programming errors, e.g. table not found or already exists, syntax error in the SQL statement, wrong number of parameters specified, etc. """ pass class DataError(DatabaseError): """Exception raised for errors that are due to problems with the processed data like division by zero, numeric value out of range, etc. """ pass class NotSupportedError(DatabaseError): """Exception raised in case a method or database API was used which is not supported by the database, e.g. requesting a ``.rollback()`` on a connection that does not support transaction or has transactions turned off. """ pass PK©6DH4lrx!x!prestornado/common.py"""Package private common utilities. Do not use directly. Many docstrings in this file are based on PEP-249, which is in the public domain. """ from __future__ import absolute_import from __future__ import unicode_literals from tornado.gen import coroutine, Return from prestornado import exc import abc import collections import time class DBAPICursor(object): """Base class for some common DB-API logic""" __metaclass__ = abc.ABCMeta _STATE_NONE = 0 _STATE_RUNNING = 1 _STATE_FINISHED = 2 def __init__(self, poll_interval=1): self._poll_interval = poll_interval self._reset_state() self.lastrowid = None def _reset_state(self): """Reset state about the previous query in preparation for running another query""" # State to return as part of DB-API self._rownumber = 0 # Internal helper state self._state = self._STATE_NONE self._data = collections.deque() self._columns = None @coroutine def _fetch_while(self, fn): while fn(): yield self._fetch_more() if fn(): time.sleep(self._poll_interval) @abc.abstractproperty def description(self): raise NotImplementedError # pragma: no cover def close(self): """By default, do nothing""" pass @abc.abstractmethod def _fetch_more(self): """Get more results, append it to ``self._data``, and update ``self._state``.""" raise NotImplementedError # pragma: no cover @property def rowcount(self): """By default, return -1 to indicate that this is not supported.""" return -1 @abc.abstractmethod def execute(self, operation, parameters=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). Return values are not defined. """ raise NotImplementedError # pragma: no cover @coroutine def executemany(self, operation, seq_of_parameters): """Prepare a database operation (query or command) and then execute it against all parameter sequences or mappings found in the sequence ``seq_of_parameters``. Only the final result set is retained. Return values are not defined. """ for parameters in seq_of_parameters[:-1]: yield self.execute(operation, parameters) while self._state != self._STATE_FINISHED: yield self._fetch_more() if seq_of_parameters: yield self.execute(operation, seq_of_parameters[-1]) @coroutine def fetchone(self): """Fetch the next row of a query result set, returning a single sequence, or ``None`` when no more data is available. An :py:class:`~prestornado.exc.Error` (or subclass) exception is raised if the previous call to :py:meth:`execute` did not produce any result set or no call was issued yet. """ if self._state == self._STATE_NONE: raise exc.ProgrammingError("No query yet") # Sleep until we're done or we have some data to return yield self._fetch_while(lambda: not self._data and self._state != self._STATE_FINISHED) if not self._data: raise Return(None) else: self._rownumber += 1 raise Return(self._data.popleft()) @coroutine def fetchmany(self, size=None): """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 :py:class:`~prestornado.exc.Error` (or subclass) exception is raised if the previous call to :py:meth:`execute` did not produce any result set or no call was issued yet. """ if size is None: size = self.arraysize result = [] for _ in xrange(size): one = yield self.fetchone() if one is None: break else: result.append(one) raise Return(result) @coroutine def fetchall(self): """Fetch all (remaining) rows of a query result, returning them as a sequence of sequences (e.g. a list of tuples). An :py:class:`~prestornado.exc.Error` (or subclass) exception is raised if the previous call to :py:meth:`execute` did not produce any result set or no call was issued yet. """ result = [] while True: one = yield self.fetchone() if one is None: break else: result.append(one) raise Return(result) @property def arraysize(self): """This read/write attribute specifies the number of rows to fetch at a time with :py:meth:`fetchmany`. It defaults to 1 meaning to fetch a single row at a time. """ return self._arraysize @arraysize.setter def arraysize(self, value): self._arraysize = value def setinputsizes(self, sizes): """Does nothing by default""" pass def setoutputsize(self, size, column=None): """Does nothing by default""" pass # # Optional DB API Extensions # @property def rownumber(self): """This read-only attribute should provide the current 0-based index of the cursor in the result set. The index can be seen as index of the cursor in a sequence (the result set). The next fetch operation will fetch the row indexed by ``rownumber`` in that sequence. """ return self._rownumber def next(self): """Return the next row from the currently executing SQL statement using the same semantics as :py:meth:`fetchone`. A ``StopIteration`` exception is raised when the result set is exhausted. """ raise NotImplementedError() def __iter__(self): """Return self to make cursors compatible to the iteration protocol.""" return self class DBAPITypeObject(object): # Taken from http://www.python.org/dev/peps/pep-0249/#implementation-hints def __init__(self, *values): self.values = values def __cmp__(self, other): if other in self.values: return 0 if other < self.values: return 1 else: return -1 class ParamEscaper(object): def escape_args(self, parameters): if isinstance(parameters, dict): return {k: self.escape_item(v) for k, v in parameters.iteritems()} elif isinstance(parameters, (list, tuple)): return tuple(self.escape_item(x) for x in parameters) else: raise exc.ProgrammingError("Unsupported param format: {}".format(parameters)) def escape_number(self, item): return item def escape_string(self, item): # Need to decode UTF-8 because of old sqlalchemy. # Newer SQLAlchemy checks dialect.supports_unicode_binds before encoding Unicode strings # as byte strings. The old version always encodes Unicode as byte strings, which breaks # string formatting here. if isinstance(item, str): item = item.decode('utf-8') # This is good enough when backslashes are literal, newlines are just followed, and the way # to escape a single quote is to put two single quotes. # (i.e. only special character is single quote) return "'{}'".format(item.replace("'", "''")) def escape_item(self, item): if isinstance(item, (int, long, float)): return self.escape_number(item) elif isinstance(item, basestring): return self.escape_string(item) else: raise exc.ProgrammingError("Unsupported object {}".format(item)) class UniversalSet(object): """set containing everything""" def __contains__(self, item): return True PK}4DH5¤2 Å Å prestornado/presto.py"""DB-API implementation backed by Presto See http://www.python.org/dev/peps/pep-0249/ Many docstrings in this file are based on the PEP, which is in the public domain. """ from __future__ import absolute_import from __future__ import unicode_literals from prestornado import common from prestornado.common import DBAPITypeObject # Make all exceptions visible in this module per DB-API from prestornado.exc import * from tornado.escape import json_decode from tornado.gen import coroutine, Return from tornado.httpclient import AsyncHTTPClient, HTTPRequest import base64 import getpass import logging import urlparse # PEP 249 module globals apilevel = '2.0' threadsafety = 2 # Threads may share the module and connections. paramstyle = 'pyformat' # Python extended format codes, e.g. ...WHERE name=%(name)s _logger = logging.getLogger(__name__) _escaper = common.ParamEscaper() def connect(*args, **kwargs): """Constructor for creating a connection to the database. See class :py:class:`Connection` for arguments. :returns: a :py:class:`Connection` object. """ return Connection(*args, **kwargs) class Connection(object): """Presto does not have a notion of a persistent connection. Thus, these objects are small stateless factories for cursors, which do all the real work. """ def __init__(self, *args, **kwargs): self._args = args self._kwargs = kwargs def close(self): """Presto does not have anything to close""" # TODO cancel outstanding queries? pass def commit(self): """Presto does not support transactions""" pass def cursor(self): """Return a new :py:class:`Cursor` object using the connection.""" return Cursor(*self._args, **self._kwargs) def rollback(self): raise NotSupportedError("Presto does not have transactions") # pragma: no cover class Cursor(common.DBAPICursor): """These objects represent a database cursor, which is used to manage the context of a fetch operation. Cursors are not isolated, i.e., any changes done to the database by a cursor are immediately visible by other cursors or connections. """ def __init__(self, host, port='8080', username=None, catalog='hive', schema='default', poll_interval=1, source='prestornado'): """ :param host: hostname to connect to, e.g. ``presto.example.com`` :param port: int -- port, defaults to 8080 :param user: string -- defaults to system user name :param catalog: string -- defaults to ``hive`` :param schema: string -- defaults to ``default`` :param poll_interval: int -- how often to ask the Presto REST interface for a progress update, defaults to a second :param source: string -- arbitrary identifier (shows up in the Presto monitoring page) """ super(Cursor, self).__init__(poll_interval) # Config self._host = host self._port = port self._username = username or getpass.getuser() self._catalog = catalog self._schema = schema self._arraysize = 1 self._poll_interval = poll_interval self._source = source self._reset_state() def _reset_state(self): """Reset state about the previous query in preparation for running another query""" super(Cursor, self)._reset_state() self._nextUri = None self._columns = None @property def description(self): """This read-only attribute is a sequence of 7-item sequences. Each of these sequences contains information describing one result column: - name - type_code - display_size (None in current implementation) - internal_size (None in current implementation) - precision (None in current implementation) - scale (None in current implementation) - null_ok (always True in current implementation) The ``type_code`` can be interpreted by comparing it to the Type Objects specified in the section below. """ if self._columns is None: return None return [ # name, type_code, display_size, internal_size, precision, scale, null_ok (col['name'], col['type'], None, None, None, None, True) for col in self._columns ] @coroutine def execute(self, operation, parameters=None): """Prepare and execute a database operation (query or command). Return values are not defined. """ headers = { 'X-Presto-Catalog': self._catalog, 'X-Presto-Schema': self._schema, 'X-Presto-Source': self._source, 'X-Presto-User': self._username, } # Prepare statement if parameters is None: sql = operation else: sql = operation % _escaper.escape_args(parameters) self._reset_state() self._state = self._STATE_RUNNING url = urlparse.urlunparse(( 'http', '{}:{}'.format(self._host, self._port), '/v1/statement', None, None, None)) _logger.info('%s', sql) _logger.debug("Headers: %s", headers) request = HTTPRequest(url, method='POST', body=sql.encode('utf-8'), headers=headers) client = AsyncHTTPClient(max_clients=512) self._process_response((yield client.fetch(request, raise_error=False))) @coroutine def poll(self): """Poll for and return the raw status data provided by the Presto REST API. :returns: dict -- JSON status information or ``None`` if the query is done :raises: ``ProgrammingError`` when no query has been started .. note:: This is not a part of DB-API. """ if self._state == self._STATE_NONE: raise ProgrammingError("No query yet") if self._nextUri is None: assert self._state == self._STATE_FINISHED, "Should be finished if nextUri is None" raise Return(None) request = HTTPRequest(self._nextUri) client = AsyncHTTPClient(max_clients=512) response = yield client.fetch(request) self._process_response(response) raise Return(json_decode(response.body)) @coroutine def _fetch_more(self): """Fetch the next URI and update state""" request = HTTPRequest(self._nextUri) client = AsyncHTTPClient(max_clients=512) self._process_response((yield client.fetch(request))) def _decode_binary(self, rows): # As of Presto 0.69, binary data is returned as the varbinary type in base64 format # This function decodes base64 data in place for i, col in enumerate(self.description): if col[1] == 'varbinary': for row in rows: row[i] = base64.b64decode(row[i]) def _process_response(self, response): """Given the JSON response from Presto's REST API, update the internal state with the next URI and any data from the response """ # TODO handle HTTP 503 if response.code != 200: fmt = "Unexpected status code {}\n{}" raise OperationalError(fmt.format(response.code, response.body)) response_json = json_decode(response.body) _logger.debug("Got response %s", response_json) assert self._state == self._STATE_RUNNING, "Should be running if processing response" self._nextUri = response_json.get('nextUri') self._columns = response_json.get('columns') if 'data' in response_json: assert self._columns new_data = response_json['data'] self._decode_binary(new_data) self._data += new_data if 'nextUri' not in response_json: self._state = self._STATE_FINISHED if 'error' in response_json: assert not self._nextUri, "Should not have nextUri if failed" raise DatabaseError(response_json['error']) # # Type Objects and Constructors # # See types in presto-main/src/main/java/com/facebook/presto/tuple/TupleInfo.java FIXED_INT_64 = DBAPITypeObject(['bigint']) VARIABLE_BINARY = DBAPITypeObject(['varchar']) DOUBLE = DBAPITypeObject(['double']) BOOLEAN = DBAPITypeObject(['boolean']) PK¦DHiȺïprestornado/__init__.py__version__ = '0.1.5' PK«DHprestornado/tests/__init__.pyPKy9DH£.xžž%prestornado/tests/test_prestornado.py"""Presto integration tests. These rely on having a Presto+Hadoop cluster set up. They also require a tables created by make_test_tables.sh. """ from __future__ import absolute_import from __future__ import unicode_literals from tornado import gen from tornado.concurrent import Future from tornado.httpclient import HTTPResponse, HTTPRequest, AsyncHTTPClient from tornado.testing import AsyncTestCase, gen_test from prestornado.tests.dbapi_test_case import with_cursor, DBAPITestCase from prestornado import exc from prestornado import presto from StringIO import StringIO import mock _HOST = 'prestodb' class TestPresto(AsyncTestCase, DBAPITestCase): def connect(self): return presto.connect(host=_HOST, source=self.id()) def run_gen(self, f): f() return self.wait() def setup_fetch(self, fetch_mock, status_code, body=None): """Copied from https://groups.google.com/forum/#!topic/python-tornado/LrXqiL6InTM""" def side_effect(request, **kwargs): if request is not HTTPRequest: request = HTTPRequest(request) buffer = StringIO(body) response = HTTPResponse(request, status_code, None, buffer) future = Future() future.set_result(response) return future fetch_mock.side_effect = side_effect @with_cursor @gen_test def test_description(self, cursor): yield cursor.execute('SELECT 1 AS foobar FROM one_row') # wait to finish while (yield cursor.poll()): pass self.assertEqual(cursor.description, [('foobar', 'bigint', None, None, None, None, True)]) @with_cursor @gen_test def test_complex(self, cursor): yield cursor.execute('SELECT * FROM one_row_complex') # wait to finish while (yield cursor.poll()): pass # TODO Presto drops the union and decimal fields self.assertEqual(cursor.description, [ ('boolean', 'boolean', None, None, None, None, True), ('tinyint', 'bigint', None, None, None, None, True), ('smallint', 'bigint', None, None, None, None, True), ('int', 'bigint', None, None, None, None, True), ('bigint', 'bigint', None, None, None, None, True), ('float', 'double', None, None, None, None, True), ('double', 'double', None, None, None, None, True), ('string', 'varchar', None, None, None, None, True), ('timestamp', 'timestamp', None, None, None, None, True), ('binary', 'varbinary', None, None, None, None, True), ('array', 'array', None, None, None, None, True), ('map', 'map', None, None, None, None, True), ('struct', "row('a','b')", None, None, None, None, True), #('union', 'varchar', None, None, None, None, True), #('decimal', 'double', None, None, None, None, True), ]) data = yield cursor.fetchall() self.assertEqual(data, [[ True, 127, 32767, 2147483647, 9223372036854775807, 0.5, 0.25, 'a string', '1970-01-01 08:00:00.000', '123', [1, 2], {"1": 2, "3": 4}, # Presto converts all keys to strings so that they're valid JSON [1, 2], # struct is returned as a list of elements #'{0:1}', #0.1, ]]) def test_noops(self): """The DB-API specification requires that certain actions exist, even though they might not be applicable.""" # Wohoo inflating coverage stats! connection = self.connect() cursor = connection.cursor() self.assertEqual(cursor.rowcount, -1) cursor.setinputsizes([]) cursor.setoutputsize(1, 'blah') connection.commit() @mock.patch.object(AsyncHTTPClient, 'fetch') def test_non_200(self, fetch): self.setup_fetch(fetch, 404, '') cursor = self.connect().cursor() @gen.engine def f(): yield cursor.execute('show tables') self.stop() self.assertRaises(exc.OperationalError, self.run_gen, f) @with_cursor @gen_test def test_poll(self, cursor): @gen.engine def f(): yield cursor.poll() self.stop() self.assertRaises(presto.ProgrammingError, self.run_gen, f) yield cursor.execute('SELECT * FROM one_row') while True: status = yield cursor.poll() if status is None: break self.assertIn('stats', status) def fail(*args, **kwargs): self.fail("Should not need requests.get after done polling") # pragma: no cover with mock.patch.object(AsyncHTTPClient, 'fetch') as fetch: fetch.side_effect = fail self.assertEqual((yield cursor.fetchall()), [[1]]) PK÷6DH´>u³³$prestornado/tests/dbapi_test_case.py# encoding: utf-8 """Shared DB-API test cases""" from __future__ import absolute_import from __future__ import unicode_literals from prestornado import exc from tornado.testing import gen_test from tornado import gen import abc import contextlib import functools def with_cursor(fn): """Pass a cursor to the given function and handle cleanup. The cursor is taken from ``self.connect()``. """ @functools.wraps(fn) def wrapped_fn(self, *args, **kwargs): with contextlib.closing(self.connect()) as connection: with contextlib.closing(connection.cursor()) as cursor: fn(self, cursor, *args, **kwargs) return wrapped_fn class DBAPITestCase(object): __metaclass__ = abc.ABCMeta @abc.abstractmethod def connect(self): raise NotImplementedError # pragma: no cover @with_cursor @gen_test def test_fetchone(self, cursor): yield cursor.execute('SELECT * FROM one_row') self.assertEqual(cursor.rownumber, 0) self.assertEqual((yield cursor.fetchone()), [1]) self.assertEqual(cursor.rownumber, 1) self.assertIsNone((yield cursor.fetchone())) @with_cursor @gen_test def test_fetchall(self, cursor): yield cursor.execute('SELECT * FROM one_row') self.assertEqual((yield cursor.fetchall()), [[1]]) yield cursor.execute('SELECT a FROM many_rows ORDER BY a') self.assertEqual((yield cursor.fetchall()), [[i] for i in xrange(10000)]) @with_cursor def test_description_initial(self, cursor): self.assertIsNone(cursor.description) @with_cursor @gen_test def test_description_failed(self, cursor): try: yield cursor.execute('blah_blah') except exc.DatabaseError: pass self.assertIsNone(cursor.description) @with_cursor def test_bad_query(self, cursor): @gen.engine def run(): yield cursor.execute('SELECT does_not_exist FROM this_really_does_not_exist') yield cursor.fetchone() self.assertRaises(exc.DatabaseError, self.run_gen, run) @with_cursor @gen_test def test_concurrent_execution(self, cursor): yield cursor.execute('SELECT * FROM one_row') yield cursor.execute('SELECT * FROM one_row') self.assertEqual((yield cursor.fetchall()), [[1]]) @with_cursor @gen_test def test_executemany(self, cursor): for length in 1, 2: yield cursor.executemany( 'SELECT %(x)d FROM one_row', [{'x': i} for i in xrange(1, length + 1)] ) self.assertEqual((yield cursor.fetchall()), [[length]]) @with_cursor @gen_test def test_executemany_none(self, cursor): @gen.engine def g(): yield cursor.fetchone() yield cursor.executemany('should_never_get_used', []) self.assertIsNone(cursor.description) self.assertRaises(exc.ProgrammingError, self.run_gen, g) @with_cursor def test_fetchone_no_data(self, cursor): @gen.engine def f(): yield cursor.fetchone() self.assertRaises(exc.ProgrammingError, self.run_gen, f) @with_cursor @gen_test def test_fetchmany(self, cursor): yield cursor.execute('SELECT * FROM many_rows LIMIT 15') self.assertEqual((yield cursor.fetchmany(0)), []) self.assertEqual(len((yield cursor.fetchmany(10))), 10) self.assertEqual(len((yield cursor.fetchmany(10))), 5) @with_cursor @gen_test def test_arraysize(self, cursor): cursor.arraysize = 5 yield cursor.execute('SELECT * FROM many_rows LIMIT 20') self.assertEqual(len((yield cursor.fetchmany())), 5) @with_cursor @gen_test def test_polling_loop(self, cursor): """Try to trigger the polling logic in fetchone()""" cursor._poll_interval = 0 yield cursor.execute('SELECT COUNT(*) FROM many_rows') self.assertEqual((yield cursor.fetchone()), [10000]) @with_cursor @gen_test def test_no_params(self, cursor): yield cursor.execute("SELECT '%(x)s' FROM one_row") self.assertEqual((yield cursor.fetchall()), [['%(x)s']]) def test_escape(self): """Verify that funny characters can be escaped as strings and SELECTed back""" bad_str = '''`~!@#$%^&*()_+-={}[]|\\;:'",./<>?\n\r\t ''' self.run_escape_case(bad_str) @with_cursor @gen_test def run_escape_case(self, cursor, bad_str): yield cursor.execute( 'SELECT %d, %s FROM one_row', (1, bad_str) ) self.assertEqual((yield cursor.fetchall()), [[1, bad_str]]) yield cursor.execute( 'SELECT %(a)d, %(b)s FROM one_row', {'a': 1, 'b': bad_str} ) self.assertEqual((yield cursor.fetchall()), [[1, bad_str]]) @with_cursor @gen_test def test_invalid_params(self, cursor): @gen.engine def f1(): yield cursor.execute('', 'hi') @gen.engine def f2(): yield cursor.execute('', [{}]) self.assertRaises(exc.ProgrammingError, self.run_gen, f1) self.assertRaises(exc.ProgrammingError, self.run_gen, f2) def test_open_close(self): with contextlib.closing(self.connect()): pass with contextlib.closing(self.connect()) as connection: with contextlib.closing(connection.cursor()): pass @with_cursor @gen_test def test_unicode(self, cursor): unicode_str = "王兢" yield cursor.execute( 'SELECT %s FROM one_row', (unicode_str,) ) self.assertEqual((yield cursor.fetchall()), [[unicode_str]]) PKADH^-Ò +prestornado-0.1.7.dist-info/DESCRIPTION.rstUNKNOWN PKADHýF"´’’)prestornado-0.1.7.dist-info/metadata.json{"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Topic :: Database"], "extensions": {"python.details": {"contacts": [{"email": "jianingy.yang@gmail.com", "name": "Jianing Yang", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/jianingy/prestornado"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "metadata_version": "2.0", "name": "prestornado", "run_requires": [{"requires": ["tornado (>=4.3)"]}], "summary": "Asynchronous PrestoDB DB-API for Tornado Web Server", "version": "0.1.7"}PKà|DHf$¤J..$prestornado-0.1.7.dist-info/pbr.json{"is_release": true, "git_version": "8c9a605"}PK@DHá7Éü )prestornado-0.1.7.dist-info/top_level.txtprestornado PKADHŒ''\\!prestornado-0.1.7.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any PKADH–íÜçç$prestornado-0.1.7.dist-info/METADATAMetadata-Version: 2.0 Name: prestornado Version: 0.1.7 Summary: Asynchronous PrestoDB DB-API for Tornado Web Server Home-page: https://github.com/jianingy/prestornado Author: Jianing Yang Author-email: jianingy.yang@gmail.com License: UNKNOWN Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Topic :: Database Requires-Dist: tornado (>=4.3) UNKNOWN PKADHZÚð¨¨"prestornado-0.1.7.dist-info/RECORDprestornado/__init__.py,sha256=DQiXHqQ3C29f_--ye7aVMDPnITrl9M0zMU42LYY48zE,22 prestornado/common.py,sha256=dD5nDXmuEOho92pJNp8SjvNToABGbpfOBkdFoSXtloc,8568 prestornado/exc.py,sha256=QgtRk1Uku4qQke92ZnAUztBVSQI8gXrar4g0CC_URHY,2204 prestornado/presto.py,sha256=G_z6ivLxbdEwavRBcGC_Ky0aQxPU2WkJVDAMGNpPhKg,8389 prestornado/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 prestornado/tests/dbapi_test_case.py,sha256=B6PruiU3ieFIOEnA1Gm-I_TrzRiXhczYn3kOuCZHQg8,5811 prestornado/tests/test_prestornado.py,sha256=7IPOz1ZjZsbRu6PaFociSJrZlV9wUVeRVHABMq1CroI,5022 prestornado-0.1.7.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10 prestornado-0.1.7.dist-info/METADATA,sha256=iWt-mqYKf58ANLywTrlViVQ6iHp8KK6_-aVbC_EoX4Q,487 prestornado-0.1.7.dist-info/RECORD,, prestornado-0.1.7.dist-info/WHEEL,sha256=JTb7YztR8fkPg6aSjc571Q4eiVHCwmUDlX8PhuuqIIE,92 prestornado-0.1.7.dist-info/metadata.json,sha256=HETcwieO4PCIn7wKuknIQ9IX6GemYpakgKT2THJh91U,658 prestornado-0.1.7.dist-info/pbr.json,sha256=IjdNn2hC8IdICwrSXm70-IF0ESNF4EU69_sFQcy1zFY,46 prestornado-0.1.7.dist-info/top_level.txt,sha256=TG-pW2Gu31Mc7qiuDPPPEnJ4G--3b2o8DnNm9WU_nOI,12 PKKDHÐG݃œœprestornado/exc.pyPK©6DH4lrx!x!Ìprestornado/common.pyPK}4DH5¤2 Å Å w*prestornado/presto.pyPK¦DHiȺïoKprestornado/__init__.pyPK«DHºKprestornado/tests/__init__.pyPKy9DH£.xžž%õKprestornado/tests/test_prestornado.pyPK÷6DH´>u³³$Ö_prestornado/tests/dbapi_test_case.pyPKADH^-Ò +Ëvprestornado-0.1.7.dist-info/DESCRIPTION.rstPKADHýF"´’’)wprestornado-0.1.7.dist-info/metadata.jsonPKà|DHf$¤J..$÷yprestornado-0.1.7.dist-info/pbr.jsonPK@DHá7Éü )gzprestornado-0.1.7.dist-info/top_level.txtPKADHŒ''\\!ºzprestornado-0.1.7.dist-info/WHEELPKADH–íÜçç$U{prestornado-0.1.7.dist-info/METADATAPKADHZÚð¨¨"~}prestornado-0.1.7.dist-info/RECORDPKEf‚