connection.py 6.38 KB
Newer Older
Lukáš Lalinský's avatar
Lukáš Lalinský committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14
# Copyright 2015 Lukas Lalinsky
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

Lukáš Lalinský's avatar
Lukáš Lalinský committed
15 16 17 18
import logging
import uuid
import weakref
from phoenixdb import errors
19
from phoenixdb.avatica import OPEN_CONNECTION_PROPERTIES
Lukáš Lalinský's avatar
Lukáš Lalinský committed
20 21 22 23 24 25 26 27 28 29
from phoenixdb.cursor import Cursor
from phoenixdb.errors import OperationalError, NotSupportedError, ProgrammingError

__all__ = ['Connection']

logger = logging.getLogger(__name__)


class Connection(object):
    """Database connection.
30

Lukáš Lalinský's avatar
Lukáš Lalinský committed
31 32 33
    You should not construct this object manually, use :func:`~phoenixdb.connect` instead.
    """

34 35 36 37 38 39
    cursor_factory = None
    """
    The default cursor factory used by :meth:`cursor` if the parameter is not specified.
    """

    def __init__(self, client, cursor_factory=None, **kwargs):
Lukáš Lalinský's avatar
Lukáš Lalinský committed
40 41
        self._client = client
        self._closed = False
42 43 44 45
        if cursor_factory is not None:
            self.cursor_factory = cursor_factory
        else:
            self.cursor_factory = Cursor
Lukáš Lalinský's avatar
Lukáš Lalinský committed
46
        self._cursors = []
47 48 49 50 51 52 53 54 55
        # Extract properties to pass to OpenConnectionRequest
        self._connection_args = {}
        # The rest of the kwargs
        self._filtered_args = {}
        for k in kwargs:
            if k in OPEN_CONNECTION_PROPERTIES:
                self._connection_args[k] = kwargs[k]
            else:
                self._filtered_args[k] = kwargs[k]
56
        self.open()
57
        self.set_session(**self._filtered_args)
Lukáš Lalinský's avatar
Lukáš Lalinský committed
58 59 60 61 62 63 64 65 66 67 68 69

    def __del__(self):
        if not self._closed:
            self.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if not self._closed:
            self.close()

70 71 72
    def open(self):
        """Opens the connection."""
        self._id = str(uuid.uuid4())
73
        self._client.open_connection(self._id, info=self._connection_args)
74

Lukáš Lalinský's avatar
Lukáš Lalinský committed
75 76 77
    def close(self):
        """Closes the connection.
        No further operations are allowed, either on the connection or any
78 79 80 81
        of its cursors, once the connection is closed.

        If the connection is used in a ``with`` statement, this method will
        be automatically called at the end of the ``with`` block.
Lukáš Lalinský's avatar
Lukáš Lalinský committed
82 83 84 85 86 87 88
        """
        if self._closed:
            raise ProgrammingError('the connection is already closed')
        for cursor_ref in self._cursors:
            cursor = cursor_ref()
            if cursor is not None and not cursor._closed:
                cursor.close()
89
        self._client.close_connection(self._id)
Lukáš Lalinský's avatar
Lukáš Lalinský committed
90 91 92
        self._client.close()
        self._closed = True

93 94 95 96 97
    @property
    def closed(self):
        """Read-only attribute specifying if the connection is closed or not."""
        return self._closed

Lukáš Lalinský's avatar
Lukáš Lalinský committed
98 99 100 101 102 103 104
    def commit(self):
        """Commits pending database changes.

        Currently, this does nothing, because the RPC does not support
        transactions. Only defined for DB API 2.0 compatibility.
        You need to use :attr:`autocommit` mode.
        """
105
        # TODO can support be added for this?
Lukáš Lalinský's avatar
Lukáš Lalinský committed
106 107 108
        if self._closed:
            raise ProgrammingError('the connection is already closed')

109
    def cursor(self, cursor_factory=None):
Lukáš Lalinský's avatar
Lukáš Lalinský committed
110 111
        """Creates a new cursor.

112 113 114 115 116 117 118
        :param cursor_factory:
            This argument can be used to create non-standard cursors.
            The class returned must be a subclass of
            :class:`~phoenixdb.cursor.Cursor` (for example :class:`~phoenixdb.cursor.DictCursor`).
            A default factory for the connection can also be specified using the
            :attr:`cursor_factory` attribute.

Lukáš Lalinský's avatar
Lukáš Lalinský committed
119 120 121 122 123
        :returns:
            A :class:`~phoenixdb.cursor.Cursor` object.
        """
        if self._closed:
            raise ProgrammingError('the connection is already closed')
124
        cursor = (cursor_factory or self.cursor_factory)(self)
Lukáš Lalinský's avatar
Lukáš Lalinský committed
125 126 127 128 129
        self._cursors.append(weakref.ref(cursor, self._cursors.remove))
        return cursor

    def set_session(self, autocommit=None, readonly=None):
        """Sets one or more parameters in the current connection.
130

Lukáš Lalinský's avatar
Lukáš Lalinský committed
131 132 133 134
        :param autocommit:
            Switch the connection to autocommit mode. With the current
            version, you need to always enable this, because
            :meth:`commit` is not implemented.
135

Lukáš Lalinský's avatar
Lukáš Lalinský committed
136 137 138 139 140 141 142 143
        :param readonly:
            Switch the connection to read-only mode.
        """
        props = {}
        if autocommit is not None:
            props['autoCommit'] = bool(autocommit)
        if readonly is not None:
            props['readOnly'] = bool(readonly)
144
        props = self._client.connection_sync(self._id, props)
Mark Heppner's avatar
Mark Heppner committed
145 146 147
        self._autocommit = props.auto_commit
        self._readonly = props.read_only
        self._transactionisolation = props.transaction_isolation
Lukáš Lalinský's avatar
Lukáš Lalinský committed
148 149 150

    @property
    def autocommit(self):
151
        """Read/write attribute for switching the connection's autocommit mode."""
Lukáš Lalinský's avatar
Lukáš Lalinský committed
152 153 154 155 156 157
        return self._autocommit

    @autocommit.setter
    def autocommit(self, value):
        if self._closed:
            raise ProgrammingError('the connection is already closed')
158
        props = self._client.connection_sync(self._id, {'autoCommit': bool(value)})
Mark Heppner's avatar
Mark Heppner committed
159
        self._autocommit = props.auto_commit
Lukáš Lalinský's avatar
Lukáš Lalinský committed
160 161 162

    @property
    def readonly(self):
163
        """Read/write attribute for switching the connection's readonly mode."""
Lukáš Lalinský's avatar
Lukáš Lalinský committed
164 165 166 167 168 169
        return self._readonly

    @readonly.setter
    def readonly(self, value):
        if self._closed:
            raise ProgrammingError('the connection is already closed')
170
        props = self._client.connection_sync(self._id, {'readOnly': bool(value)})
Mark Heppner's avatar
Mark Heppner committed
171
        self._readonly = props.read_only
Lukáš Lalinský's avatar
Lukáš Lalinský committed
172

173 174 175 176 177 178 179 180
    @property
    def transactionisolation(self):
        return self._transactionisolation

    @readonly.setter
    def transactionisolation(self, value):
        if self._closed:
            raise ProgrammingError('the connection is already closed')
181
        props = self._client.connection_sync(self._id, {'transactionIsolation': bool(value)})
182
        self._transactionisolation = props.transaction_isolation
Lukáš Lalinský's avatar
Lukáš Lalinský committed
183

184

Lukáš Lalinský's avatar
Lukáš Lalinský committed
185 186
for name in errors.__all__:
    setattr(Connection, name, getattr(errors, name))