Skip to content

Commit

Permalink
Rework aiopg.sa.result module with Cython.
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreiPashkin committed Mar 18, 2020
1 parent c83e6a5 commit b6ac025
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
env:

install:
- pip install -U setuptools
- pip install -U setuptools cython
- python setup.py install
- pip install -Ur requirements.txt
- pip install codecov
Expand Down
58 changes: 37 additions & 21 deletions aiopg/sa/result.py → aiopg/sa/result.pyx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import weakref
from collections.abc import Mapping, Sequence

#cython: language_level=3
from sqlalchemy.sql import expression, sqltypes

from . import exc


class RowProxy(Mapping):
__slots__ = ('_result_proxy', '_row', '_processors', '_keymap')
cdef class RowProxy:
cdef ResultMetaData _result_proxy
cdef tuple _row
cdef list _processors
cdef dict _keymap

def __init__(self, result_proxy, row, processors, keymap):
def __cinit__(self, result_proxy, row, processors, keymap):
"""RowProxy objects are constructed by ResultProxy objects."""
self._result_proxy = result_proxy
self._row = row
Expand All @@ -22,11 +23,12 @@ def __iter__(self):
def __len__(self):
return len(self._row)

def __getitem__(self, key):
cdef object _getitem(self, key):
cdef int index
try:
processor, obj, index = self._keymap[key]
processor, _, index = self._keymap[key]
except KeyError:
processor, obj, index = self._result_proxy._key_fallback(key)
processor, _, index = self._result_proxy._key_fallback(key)
# Do we need slicing at all? RowProxy now is Mapping not Sequence
# except TypeError:
# if isinstance(key, slice):
Expand All @@ -49,38 +51,47 @@ def __getitem__(self, key):
else:
return self._row[index]

def __getattr__(self, name):
def __getitem__(self, item):
return self._getitem(item)

cdef object _getattr(self, name):
try:
return self[name]
except KeyError as e:
raise AttributeError(e.args[0])

def __getattr__(self, item):
return self._getattr(item)

def __contains__(self, key):
return self._result_proxy._has_key(self._row, key)

__hash__ = None

def __eq__(self, other):
if isinstance(other, RowProxy):
if hasattr(other, 'as_tuple'):
return self.as_tuple() == other.as_tuple()
elif isinstance(other, Sequence):
elif hasattr(other, 'index') and hasattr(other, 'count'):
return self.as_tuple() == other
else:
return NotImplemented

def __ne__(self, other):
return not self == other

def as_tuple(self):
cdef tuple as_tuple(self):
return tuple(self[k] for k in self)

def __repr__(self):
return repr(self.as_tuple())


class ResultMetaData(object):
cdef class ResultMetaData:
"""Handle cursor.description, applying additional info from an execution
context."""
cdef public list _processors
cdef public list keys
cdef public dict _keymap

def __init__(self, result_proxy, cursor_description):
self._processors = processors = []
Expand Down Expand Up @@ -209,7 +220,7 @@ def _has_key(self, row, key):
return self._key_fallback(key, False) is not None


class ResultProxy:
cdef class ResultProxy:
"""Wraps a DB-API cursor object to provide easier access to row columns.
Individual columns may be accessed by their integer position,
Expand All @@ -228,15 +239,20 @@ class ResultProxy:
data using sqlalchemy TypeEngine objects, which are referenced from
the originating SQL statement that produced this result set.
"""

def __init__(self, connection, cursor, dialect, result_map=None):
cdef object _dialect
cdef public object _result_map
cdef object _cursor
cdef object _connection
cdef object _rowcount
cdef object _metadata

def __cinit__(self, connection, cursor, dialect, result_map=None):
self._dialect = dialect
self._result_map = result_map
self._cursor = cursor
self._connection = connection
self._rowcount = cursor.rowcount
self._metadata = None
self._weak = None
self._init_metadata()

@property
Expand Down Expand Up @@ -290,10 +306,8 @@ def _init_metadata(self):
cursor_description = self.cursor.description
if cursor_description is not None:
self._metadata = ResultMetaData(self, cursor_description)
self._weak = weakref.ref(self, lambda wr: self.cursor.close())
else:
self.close()
self._weak = None

@property
def returns_rows(self):
Expand Down Expand Up @@ -333,7 +347,9 @@ def close(self):
self.cursor.close()
# allow consistent errors
self._cursor = None
self._weak = None

def __del__(self):
self.close()

def __aiter__(self):
return self
Expand Down
16 changes: 14 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import os
import re

from setuptools import setup, find_packages
from setuptools import setup, find_packages, Extension
from Cython.Build import cythonize

CFLAGS = ['-O3']

install_requires = ['psycopg2-binary>=2.7.0']
extras_require = {'sa': ['sqlalchemy[postgresql_psycopg2binary]>=1.1']}
Expand Down Expand Up @@ -50,6 +53,14 @@ def read_changelog(path='CHANGES.txt'):
'Framework :: AsyncIO',
]

EXTENSIONS = [
Extension(
"aiopg.sa.result",
["aiopg/sa/result.pyx"],
extra_compile_args=CFLAGS
)
]

setup(
name='aiopg',
version=read_version(),
Expand All @@ -76,5 +87,6 @@ def read_changelog(path='CHANGES.txt'):
packages=find_packages(),
install_requires=install_requires,
extras_require=extras_require,
include_package_data=True
include_package_data=True,
ext_modules=cythonize(EXTENSIONS)
)

0 comments on commit b6ac025

Please sign in to comment.