Document reactors, metadata, rename pyev* to libev*
This commit is contained in:
		| @@ -18,10 +18,10 @@ log.addHandler(handler) | |||||||
|  |  | ||||||
| supported_reactors = [AsyncoreConnection] | supported_reactors = [AsyncoreConnection] | ||||||
| try: | try: | ||||||
|     from cassandra.io.pyevreactor import PyevConnection |     from cassandra.io.libevreactor import LibevConnection | ||||||
|     supported_reactors.append(PyevConnection) |     supported_reactors.append(LibevConnection) | ||||||
| except ImportError, exc: | except ImportError, exc: | ||||||
|     log.warning("Not benchmarking pyev reactor: %s" % (exc,)) |     log.warning("Not benchmarking libev reactor: %s" % (exc,)) | ||||||
|  |  | ||||||
| KEYSPACE = "testkeyspace" | KEYSPACE = "testkeyspace" | ||||||
| TABLE = "testtable" | TABLE = "testtable" | ||||||
|   | |||||||
| @@ -152,9 +152,21 @@ class WriteTimeout(Timeout): | |||||||
|  |  | ||||||
|  |  | ||||||
| class AlreadyExists(Exception): | class AlreadyExists(Exception): | ||||||
|  |     """ | ||||||
|  |     An attempt was made to create a keyspace or table that already exists. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     keyspace = None |     keyspace = None | ||||||
|  |     """ | ||||||
|  |     The name of the keyspace that already exists, or, if an attempt was | ||||||
|  |     made to create a new table, the keyspace that the table is in. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     table = None |     table = None | ||||||
|  |     """ | ||||||
|  |     The name of the table that already exists, or, if an attempt was | ||||||
|  |     make to create a keyspace, ``None``. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     def __init__(self, keyspace=None, table=None): |     def __init__(self, keyspace=None, table=None): | ||||||
|         if table: |         if table: | ||||||
| @@ -168,4 +180,22 @@ class AlreadyExists(Exception): | |||||||
|  |  | ||||||
|  |  | ||||||
| class InvalidRequest(Exception): | class InvalidRequest(Exception): | ||||||
|  |     """ | ||||||
|  |     A query was made that was invalid for some reason, such as trying to set | ||||||
|  |     the keyspace for a connection to a nonexistent keyspace. | ||||||
|  |     """ | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Unauthorized(Exception): | ||||||
|  |     """ | ||||||
|  |     The current user is not authorized to perfom the requested operation. | ||||||
|  |     """ | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AuthenticationFailed(Exception): | ||||||
|  |     """ | ||||||
|  |     Failed to authenticate. | ||||||
|  |     """ | ||||||
|     pass |     pass | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ import weakref | |||||||
| from functools import partial | from functools import partial | ||||||
| from itertools import groupby | from itertools import groupby | ||||||
|  |  | ||||||
| from cassandra import ConsistencyLevel | from cassandra import ConsistencyLevel, AuthenticationFailed | ||||||
| from cassandra.connection import ConnectionException | from cassandra.connection import ConnectionException | ||||||
| from cassandra.decoder import (QueryMessage, ResultMessage, | from cassandra.decoder import (QueryMessage, ResultMessage, | ||||||
|                                ErrorMessage, ReadTimeoutErrorMessage, |                                ErrorMessage, ReadTimeoutErrorMessage, | ||||||
| @@ -30,8 +30,8 @@ from cassandra.policies import (RoundRobinPolicy, SimpleConvictionPolicy, | |||||||
|                                 ExponentialReconnectionPolicy, HostDistance, |                                 ExponentialReconnectionPolicy, HostDistance, | ||||||
|                                 RetryPolicy) |                                 RetryPolicy) | ||||||
| from cassandra.query import SimpleStatement, PreparedStatement, BoundStatement, bind_params | from cassandra.query import SimpleStatement, PreparedStatement, BoundStatement, bind_params | ||||||
| from cassandra.pool import (AuthenticationException, _ReconnectionHandler, | from cassandra.pool import (_ReconnectionHandler, _HostReconnectionHandler, | ||||||
|                             _HostReconnectionHandler, HostConnectionPool) |                             HostConnectionPool) | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) | log = logging.getLogger(__name__) | ||||||
| @@ -155,11 +155,11 @@ class Cluster(object): | |||||||
|     I/O with Cassandra.  These are the current options: |     I/O with Cassandra.  These are the current options: | ||||||
|  |  | ||||||
|     * :class:`cassandra.io.asyncorereactor.AsyncoreConnection` |     * :class:`cassandra.io.asyncorereactor.AsyncoreConnection` | ||||||
|     * :class:`cassandra.io.pyevreactor.PyevConnection` |     * :class:`cassandra.io.libevreactor.LibevConnection` | ||||||
|  |  | ||||||
|     By default, ``AsyncoreConnection`` will be used, which uses |     By default, ``AsyncoreConnection`` will be used, which uses | ||||||
|     the ``asyncore`` module in the Python standard library.  The |     the ``asyncore`` module in the Python standard library.  The | ||||||
|     performance is slightly worse than with ``pyev``, but it is |     performance is slightly worse than with ``libev``, but it is | ||||||
|     supported on a wider range of systems. |     supported on a wider range of systems. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
| @@ -715,7 +715,7 @@ class Session(object): | |||||||
|         else: |         else: | ||||||
|             try: |             try: | ||||||
|                 new_pool = HostConnectionPool(host, distance, self) |                 new_pool = HostConnectionPool(host, distance, self) | ||||||
|             except AuthenticationException, auth_exc: |             except AuthenticationFailed, auth_exc: | ||||||
|                 conn_exc = ConnectionException(str(auth_exc), host=host) |                 conn_exc = ConnectionException(str(auth_exc), host=host) | ||||||
|                 host.monitor.signal_connection_failure(conn_exc) |                 host.monitor.signal_connection_failure(conn_exc) | ||||||
|                 return self._pools.get(host) |                 return self._pools.get(host) | ||||||
| @@ -803,7 +803,7 @@ class _ControlReconnectionHandler(_ReconnectionHandler): | |||||||
|  |  | ||||||
|     def on_exception(self, exc, next_delay): |     def on_exception(self, exc, next_delay): | ||||||
|         # TODO only overridden to add logging, so add logging |         # TODO only overridden to add logging, so add logging | ||||||
|         if isinstance(exc, AuthenticationException): |         if isinstance(exc, AuthenticationFailed): | ||||||
|             return False |             return False | ||||||
|         else: |         else: | ||||||
|             log.debug("Error trying to reconnect control connection: %r" % (exc,)) |             log.debug("Error trying to reconnect control connection: %r" % (exc,)) | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| import errno | import errno | ||||||
| from functools import wraps | from functools import wraps, partial | ||||||
| import logging | import logging | ||||||
| from threading import Event, Lock, RLock | from threading import Event, Lock, RLock | ||||||
| from Queue import Queue | from Queue import Queue | ||||||
|  |  | ||||||
| from cassandra import ConsistencyLevel | from cassandra import ConsistencyLevel, AuthenticationFailed | ||||||
| from cassandra.marshal import int8_unpack | from cassandra.marshal import int8_unpack | ||||||
| from cassandra.decoder import (ReadyMessage, AuthenticateMessage, OptionsMessage, | from cassandra.decoder import (ReadyMessage, AuthenticateMessage, OptionsMessage, | ||||||
|                                StartupMessage, ErrorMessage, CredentialsMessage, |                                StartupMessage, ErrorMessage, CredentialsMessage, | ||||||
| @@ -41,6 +41,10 @@ NONBLOCKING = (errno.EAGAIN, errno.EWOULDBLOCK) | |||||||
|  |  | ||||||
|  |  | ||||||
| class ConnectionException(Exception): | class ConnectionException(Exception): | ||||||
|  |     """ | ||||||
|  |     An unrecoverable error was hit when attempting to use a connection, | ||||||
|  |     or the connection was already closed or defunct. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     def __init__(self, message, host=None): |     def __init__(self, message, host=None): | ||||||
|         Exception.__init__(self, message) |         Exception.__init__(self, message) | ||||||
| @@ -48,14 +52,17 @@ class ConnectionException(Exception): | |||||||
|  |  | ||||||
|  |  | ||||||
| class ConnectionBusy(Exception): | class ConnectionBusy(Exception): | ||||||
|     pass |     """ | ||||||
|  |     An attempt was made to send a message through a :class:`.Connection` that | ||||||
|  |     was already at the max number of in-flight operations. | ||||||
| class ProgrammingError(Exception): |     """ | ||||||
|     pass |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
| class ProtocolError(Exception): | class ProtocolError(Exception): | ||||||
|  |     """ | ||||||
|  |     Communication did not match the protocol that this driver expects. | ||||||
|  |     """ | ||||||
|     pass |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -214,7 +221,7 @@ class Connection(object): | |||||||
|         self.send_msg(sm, cb=self._handle_startup_response) |         self.send_msg(sm, cb=self._handle_startup_response) | ||||||
|  |  | ||||||
|     @defunct_on_error |     @defunct_on_error | ||||||
|     def _handle_startup_response(self, startup_response): |     def _handle_startup_response(self, startup_response, did_authenticate=False): | ||||||
|         if self.is_defunct: |         if self.is_defunct: | ||||||
|             return |             return | ||||||
|         if isinstance(startup_response, ReadyMessage): |         if isinstance(startup_response, ReadyMessage): | ||||||
| @@ -226,17 +233,23 @@ class Connection(object): | |||||||
|             log.debug("Got AuthenticateMessage on new Connection from %s" % self.host) |             log.debug("Got AuthenticateMessage on new Connection from %s" % self.host) | ||||||
|  |  | ||||||
|             if self.credentials is None: |             if self.credentials is None: | ||||||
|                 raise ProgrammingError('Remote end requires authentication.') |                 raise AuthenticationFailed('Remote end requires authentication.') | ||||||
|  |  | ||||||
|             self.authenticator = startup_response.authenticator |             self.authenticator = startup_response.authenticator | ||||||
|             cm = CredentialsMessage(creds=self.credentials) |             cm = CredentialsMessage(creds=self.credentials) | ||||||
|             self.send_msg(cm, cb=self._handle_startup_response) |             callback = partial(self._handle_startup_response, did_authenticate=True) | ||||||
|  |             self.send_msg(cm, cb=callback) | ||||||
|         elif isinstance(startup_response, ErrorMessage): |         elif isinstance(startup_response, ErrorMessage): | ||||||
|             log.debug("Received ErrorMessage on new Connection from %s: %s" |             log.debug("Received ErrorMessage on new Connection from %s: %s" | ||||||
|                       % (self.host, startup_response.summary_msg())) |                       % (self.host, startup_response.summary_msg())) | ||||||
|             raise ConnectionException( |             if did_authenticate: | ||||||
|                 "Failed to initialize new connection to %s: %s" |                 raise AuthenticationFailed( | ||||||
|                 % (self.host, startup_response.summary_msg())) |                     "Failed to authenticate to %s: %s" % | ||||||
|  |                     (self.host, startup_response.summary_msg())) | ||||||
|  |             else: | ||||||
|  |                 raise ConnectionException( | ||||||
|  |                     "Failed to initialize new connection to %s: %s" | ||||||
|  |                     % (self.host, startup_response.summary_msg())) | ||||||
|         else: |         else: | ||||||
|             msg = "Unexpected response during Connection setup: %r" % (startup_response,) |             msg = "Unexpected response during Connection setup: %r" % (startup_response,) | ||||||
|             log.error(msg) |             log.error(msg) | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ except ImportError: | |||||||
|     from StringIO import StringIO  # ignore flake8 warning: # NOQA |     from StringIO import StringIO  # ignore flake8 warning: # NOQA | ||||||
|  |  | ||||||
| from cassandra import (ConsistencyLevel, Unavailable, WriteTimeout, ReadTimeout, | from cassandra import (ConsistencyLevel, Unavailable, WriteTimeout, ReadTimeout, | ||||||
|                        AlreadyExists, InvalidRequest) |                        AlreadyExists, InvalidRequest, Unauthorized) | ||||||
| from cassandra.marshal import (int32_pack, int32_unpack, uint16_pack, uint16_unpack, | from cassandra.marshal import (int32_pack, int32_unpack, uint16_pack, uint16_unpack, | ||||||
|                                int8_pack, int8_unpack) |                                int8_pack, int8_unpack) | ||||||
| from cassandra.cqltypes import lookup_cqltype | from cassandra.cqltypes import lookup_cqltype | ||||||
| @@ -278,6 +278,9 @@ class UnauthorizedErrorMessage(RequestValidationException): | |||||||
|     summary = 'Unauthorized' |     summary = 'Unauthorized' | ||||||
|     error_code = 0x2100 |     error_code = 0x2100 | ||||||
|  |  | ||||||
|  |     def to_exception(self): | ||||||
|  |         return Unauthorized(self.summary_msg()) | ||||||
|  |  | ||||||
|  |  | ||||||
| class InvalidRequestException(RequestValidationException): | class InvalidRequestException(RequestValidationException): | ||||||
|     summary = 'Invalid query' |     summary = 'Invalid query' | ||||||
|   | |||||||
| @@ -43,6 +43,10 @@ def _start_loop(): | |||||||
|  |  | ||||||
|  |  | ||||||
| class AsyncoreConnection(Connection, asyncore.dispatcher): | class AsyncoreConnection(Connection, asyncore.dispatcher): | ||||||
|  |     """ | ||||||
|  |     An implementation of :class:`.Connection` that utilizes the ``asyncore`` | ||||||
|  |     module in the Python standard library for its event loop. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     _buf = "" |     _buf = "" | ||||||
|     _total_reqd_bytes = 0 |     _total_reqd_bytes = 0 | ||||||
|   | |||||||
| @@ -70,7 +70,11 @@ def defunct_on_error(f): | |||||||
|     return wrapper |     return wrapper | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class PyevConnection(Connection): | class LibevConnection(Connection): | ||||||
|  |     """ | ||||||
|  |     An implementation of :class:`.Connection` that utilizes libev through | ||||||
|  |     the pyev library for its event loop. | ||||||
|  |     """ | ||||||
| 
 | 
 | ||||||
|     _buf = "" |     _buf = "" | ||||||
|     _total_reqd_bytes = 0 |     _total_reqd_bytes = 0 | ||||||
| @@ -41,11 +41,11 @@ class Metadata(object): | |||||||
|  |  | ||||||
|     keyspaces = None |     keyspaces = None | ||||||
|     """ |     """ | ||||||
|     A map from keyspace names to matching :cls:`~.KeyspaceMetadata` instances. |     A map from keyspace names to matching :class:`~.KeyspaceMetadata` instances. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     token_map = None |     token_map = None | ||||||
|     """ A :cls:`~.TokenMap` instance. """ |     """ A :class:`~.TokenMap` instance describing the ring topology. """ | ||||||
|  |  | ||||||
|     def __init__(self, cluster): |     def __init__(self, cluster): | ||||||
|         # use a weak reference so that the Cluster object can be GC'ed. |         # use a weak reference so that the Cluster object can be GC'ed. | ||||||
| @@ -57,9 +57,19 @@ class Metadata(object): | |||||||
|         self._hosts_lock = RLock() |         self._hosts_lock = RLock() | ||||||
|  |  | ||||||
|     def export_schema_as_string(self): |     def export_schema_as_string(self): | ||||||
|  |         """ | ||||||
|  |         Returns a string that can be executed as a query in order to recreate | ||||||
|  |         the entire schema.  The string is formatted to be human readable. | ||||||
|  |         """ | ||||||
|         return "\n".join(ks.export_as_string() for ks in self.keyspaces.values()) |         return "\n".join(ks.export_as_string() for ks in self.keyspaces.values()) | ||||||
|  |  | ||||||
|     def rebuild_schema(self, keyspace, table, ks_results, cf_results, col_results): |     def rebuild_schema(self, keyspace, table, ks_results, cf_results, col_results): | ||||||
|  |         """ | ||||||
|  |         Rebuild the view of the current schema from a fresh set of rows from | ||||||
|  |         the system schema tables. | ||||||
|  |  | ||||||
|  |         For internal use only. | ||||||
|  |         """ | ||||||
|         cf_def_rows = defaultdict(list) |         cf_def_rows = defaultdict(list) | ||||||
|         col_def_rows = defaultdict(lambda: defaultdict(list)) |         col_def_rows = defaultdict(lambda: defaultdict(list)) | ||||||
|  |  | ||||||
| @@ -227,6 +237,11 @@ class Metadata(object): | |||||||
|             return None |             return None | ||||||
|  |  | ||||||
|     def rebuild_token_map(self, partitioner, token_map): |     def rebuild_token_map(self, partitioner, token_map): | ||||||
|  |         """ | ||||||
|  |         Rebuild our view of the topology from fresh rows from the | ||||||
|  |         system topology tables. | ||||||
|  |         For internal use only. | ||||||
|  |         """ | ||||||
|         if partitioner.endswith('RandomPartitioner'): |         if partitioner.endswith('RandomPartitioner'): | ||||||
|             token_cls = MD5Token |             token_cls = MD5Token | ||||||
|         elif partitioner.endswith('Murmur3Partitioner'): |         elif partitioner.endswith('Murmur3Partitioner'): | ||||||
| @@ -249,6 +264,10 @@ class Metadata(object): | |||||||
|         self.token_map = TokenMap(token_cls, tokens_to_hosts, ring) |         self.token_map = TokenMap(token_cls, tokens_to_hosts, ring) | ||||||
|  |  | ||||||
|     def get_replicas(self, key): |     def get_replicas(self, key): | ||||||
|  |         """ | ||||||
|  |         Returns a list of :class:`.Host` instances that are replicas for a given | ||||||
|  |         partition key. | ||||||
|  |         """ | ||||||
|         t = self.token_map |         t = self.token_map | ||||||
|         return t.get_replicas(t.token_cls.from_key(key)) |         return t.get_replicas(t.token_cls.from_key(key)) | ||||||
|  |  | ||||||
| @@ -272,6 +291,9 @@ class Metadata(object): | |||||||
|         return self._hosts.get(address) |         return self._hosts.get(address) | ||||||
|  |  | ||||||
|     def all_hosts(self): |     def all_hosts(self): | ||||||
|  |         """ | ||||||
|  |         Returns a list of all known :class:`.Host` instances in the cluster. | ||||||
|  |         """ | ||||||
|         with self._hosts_lock: |         with self._hosts_lock: | ||||||
|             return self._hosts.values() |             return self._hosts.values() | ||||||
|  |  | ||||||
| @@ -299,7 +321,7 @@ class KeyspaceMetadata(object): | |||||||
|  |  | ||||||
|     tables = None |     tables = None | ||||||
|     """ |     """ | ||||||
|     A map from table names to instances of :cls:`~.TableMetadata`. |     A map from table names to instances of :class:`~.TableMetadata`. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, name, durable_writes, strategy_class, strategy_options): |     def __init__(self, name, durable_writes, strategy_class, strategy_options): | ||||||
| @@ -327,16 +349,46 @@ class TableMetadata(object): | |||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     keyspace = None |     keyspace = None | ||||||
|     """ An instance of :cls:`~.KeyspaceMetadata` """ |     """ An instance of :class:`~.KeyspaceMetadata` """ | ||||||
|  |  | ||||||
|     name = None |     name = None | ||||||
|     """ The string name of the table """ |     """ The string name of the table """ | ||||||
|  |  | ||||||
|     # TODO docstrings for these |  | ||||||
|     partition_key = None |     partition_key = None | ||||||
|  |     """ | ||||||
|  |     A list of :class:`.ColumnMetadata` instances representing the columns in | ||||||
|  |     the partition key for this table.  This will always hold at least one | ||||||
|  |     column. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     clustering_key = None |     clustering_key = None | ||||||
|  |     """ | ||||||
|  |     A list of :class:`.ColumnMetadata` instances representing the columns | ||||||
|  |     in the clustering key for this table.  These are all of the | ||||||
|  |     :attr:`.primary_key` columns that are not in the :attr:`.partition_key`. | ||||||
|  |  | ||||||
|  |     Note that a table may have no clustering keys, in which case this will | ||||||
|  |     be an empty list. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def primary_key(self): | ||||||
|  |         """ | ||||||
|  |         A list of :class:`.ColumnMetadata` representing the components of | ||||||
|  |         the primary key for this table. | ||||||
|  |         """ | ||||||
|  |         return self.partition_key + self.clustering_key | ||||||
|  |  | ||||||
|     columns = None |     columns = None | ||||||
|  |     """ | ||||||
|  |     A dict mapping column names to :class:`.ColumnMetadata` instances. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     options = None |     options = None | ||||||
|  |     """ | ||||||
|  |     A dict mapping table option names to their specific settings for this | ||||||
|  |     table. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     recognized_options = ( |     recognized_options = ( | ||||||
|             "comment", "read_repair_chance",  # "local_read_repair_chance", |             "comment", "read_repair_chance",  # "local_read_repair_chance", | ||||||
| @@ -355,6 +407,11 @@ class TableMetadata(object): | |||||||
|         self.comparator = None |         self.comparator = None | ||||||
|  |  | ||||||
|     def export_as_string(self): |     def export_as_string(self): | ||||||
|  |         """ | ||||||
|  |         Returns a string of CQL queries that can be used to recreate this table | ||||||
|  |         along with all indexes on it.  The returned string is formatted to | ||||||
|  |         be human readable. | ||||||
|  |         """ | ||||||
|         ret = self.as_cql_query(formatted=True) |         ret = self.as_cql_query(formatted=True) | ||||||
|         ret += ";" |         ret += ";" | ||||||
|  |  | ||||||
| @@ -365,6 +422,11 @@ class TableMetadata(object): | |||||||
|         return ret |         return ret | ||||||
|  |  | ||||||
|     def as_cql_query(self, formatted=False): |     def as_cql_query(self, formatted=False): | ||||||
|  |         """ | ||||||
|  |         Returns a CQL query that can be used to recreate this table (index | ||||||
|  |         creations are not included).  If `formatted` is set to ``True``, | ||||||
|  |         extra whitespace will be added to make the query human readable. | ||||||
|  |         """ | ||||||
|         ret = "CREATE TABLE %s.%s (%s" % (self.keyspace.name, self.name, "\n" if formatted else "") |         ret = "CREATE TABLE %s.%s (%s" % (self.keyspace.name, self.name, "\n" if formatted else "") | ||||||
|  |  | ||||||
|         if formatted: |         if formatted: | ||||||
| @@ -476,6 +538,23 @@ class TableMetadata(object): | |||||||
|  |  | ||||||
|  |  | ||||||
| class ColumnMetadata(object): | class ColumnMetadata(object): | ||||||
|  |     """ | ||||||
|  |     A representation of a single column in a table. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     table = None | ||||||
|  |     """ The :class:`.TableMetadata` this column belongs to. """ | ||||||
|  |  | ||||||
|  |     name = None | ||||||
|  |     """ The string name of this column. """ | ||||||
|  |  | ||||||
|  |     data_type = None | ||||||
|  |  | ||||||
|  |     index = None | ||||||
|  |     """ | ||||||
|  |     If an index exists on this column, this is an instance of | ||||||
|  |     :class:`.IndexMetadata`, otherwise ``None``. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     def __init__(self, table_metadata, column_name, data_type, index_metadata=None): |     def __init__(self, table_metadata, column_name, data_type, index_metadata=None): | ||||||
|         self.table = table_metadata |         self.table = table_metadata | ||||||
| @@ -485,6 +564,10 @@ class ColumnMetadata(object): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def typestring(self): |     def typestring(self): | ||||||
|  |         """ | ||||||
|  |         A string representation of the type for this column, such as "varchar" | ||||||
|  |         or "map<string, int>". | ||||||
|  |         """ | ||||||
|         if issubclass(self.data_type, types.ReversedType): |         if issubclass(self.data_type, types.ReversedType): | ||||||
|             return self.data_type.subtypes[0].cql_parameterized_type() |             return self.data_type.subtypes[0].cql_parameterized_type() | ||||||
|         else: |         else: | ||||||
| @@ -495,6 +578,20 @@ class ColumnMetadata(object): | |||||||
|  |  | ||||||
|  |  | ||||||
| class IndexMetadata(object): | class IndexMetadata(object): | ||||||
|  |     """ | ||||||
|  |     A representation of a secondary index on a column. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     column = None | ||||||
|  |     """ | ||||||
|  |     The column (:class:`.ColumnMetadata`) this index is on. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     name = None | ||||||
|  |     """ A string name for the index. """ | ||||||
|  |  | ||||||
|  |     index_type = None | ||||||
|  |     """ A string representing the type of index. """ | ||||||
|  |  | ||||||
|     def __init__(self, column_metadata, index_name=None, index_type=None): |     def __init__(self, column_metadata, index_name=None, index_type=None): | ||||||
|         self.column = column_metadata |         self.column = column_metadata | ||||||
| @@ -502,6 +599,9 @@ class IndexMetadata(object): | |||||||
|         self.index_type = index_type |         self.index_type = index_type | ||||||
|  |  | ||||||
|     def as_cql_query(self): |     def as_cql_query(self): | ||||||
|  |         """ | ||||||
|  |         Returns a CQL query that can be used to recreate this index. | ||||||
|  |         """ | ||||||
|         table = self.column.table |         table = self.column.table | ||||||
|         return "CREATE INDEX %s ON %s.%s (%s)" % (self.name, table.keyspace.name, table.name, self.column.name) |         return "CREATE INDEX %s ON %s.%s (%s)" % (self.name, table.keyspace.name, table.name, self.column.name) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -225,7 +225,7 @@ class DCAwareRoundRobinPolicy(LoadBalancingPolicy): | |||||||
|             if dc == self.local_dc: |             if dc == self.local_dc: | ||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|             for host in current_dc_hosts[:self.used_hosts_per_remote_dc]: |             for host in list(current_dc_hosts)[:self.used_hosts_per_remote_dc]: | ||||||
|                 yield host |                 yield host | ||||||
|  |  | ||||||
|     def on_up(self, host): |     def on_up(self, host): | ||||||
|   | |||||||
| @@ -1,30 +1,42 @@ | |||||||
|  | """ | ||||||
|  | Connection pooling and host management. | ||||||
|  | """ | ||||||
|  |  | ||||||
| import logging | import logging | ||||||
| import time | import time | ||||||
| from threading import Lock, RLock, Condition | from threading import Lock, RLock, Condition | ||||||
| import traceback | import traceback | ||||||
| import weakref | import weakref | ||||||
|  |  | ||||||
| from connection import MAX_STREAM_PER_CONNECTION, ConnectionException | from cassandra import AuthenticationFailed | ||||||
|  | from cassandra.connection import MAX_STREAM_PER_CONNECTION, ConnectionException | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
| class BusyConnectionException(Exception): |  | ||||||
|     pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AuthenticationException(Exception): |  | ||||||
|     pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NoConnectionsAvailable(Exception): | class NoConnectionsAvailable(Exception): | ||||||
|  |     """ | ||||||
|  |     All existing connections to a given host are busy, or there are | ||||||
|  |     no open connections. | ||||||
|  |     """ | ||||||
|     pass |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
| class Host(object): | class Host(object): | ||||||
|  |     """ | ||||||
|  |     Represents a single Cassandra node. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     address = None |     address = None | ||||||
|  |     """ | ||||||
|  |     The IP address or hostname of the node. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     monitor = None |     monitor = None | ||||||
|  |     """ | ||||||
|  |     A :class:`.HealthMonitor` instance that tracks whether this node is | ||||||
|  |     up or down. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     _datacenter = None |     _datacenter = None | ||||||
|     _rack = None |     _rack = None | ||||||
| @@ -43,17 +55,28 @@ class Host(object): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def datacenter(self): |     def datacenter(self): | ||||||
|  |         """ The datacenter the node is in.  """ | ||||||
|         return self._datacenter |         return self._datacenter | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def rack(self): |     def rack(self): | ||||||
|  |         """ The rack the node is in.  """ | ||||||
|         return self._rack |         return self._rack | ||||||
|  |  | ||||||
|     def set_location_info(self, datacenter, rack): |     def set_location_info(self, datacenter, rack): | ||||||
|  |         """ | ||||||
|  |         Sets the datacenter and rack for this node. Intended for internal | ||||||
|  |         use (by the control connection, which periodically checks the | ||||||
|  |         ring topology) only. | ||||||
|  |         """ | ||||||
|         self._datacenter = datacenter |         self._datacenter = datacenter | ||||||
|         self._rack = rack |         self._rack = rack | ||||||
|  |  | ||||||
|     def get_and_set_reconnection_handler(self, new_handler): |     def get_and_set_reconnection_handler(self, new_handler): | ||||||
|  |         """ | ||||||
|  |         Atomically replaces the reconnection handler for this | ||||||
|  |         host.  Intended for internal use only. | ||||||
|  |         """ | ||||||
|         with self._reconnection_lock: |         with self._reconnection_lock: | ||||||
|             old = self._reconnection_handler |             old = self._reconnection_handler | ||||||
|             self._reconnection_handler = new_handler |             self._reconnection_handler = new_handler | ||||||
| @@ -137,9 +160,9 @@ class _ReconnectionHandler(object): | |||||||
|  |  | ||||||
|         Subclasses should return ``False`` if no more attempts to connection |         Subclasses should return ``False`` if no more attempts to connection | ||||||
|         should be made, ``True`` otherwise.  The default behavior is to |         should be made, ``True`` otherwise.  The default behavior is to | ||||||
|         always retry unless the error is an AuthenticationException. |         always retry unless the error is an :exc:`.AuthenticationFailed`. | ||||||
|         """ |         """ | ||||||
|         if isinstance(exc, AuthenticationException): |         if isinstance(exc, AuthenticationFailed): | ||||||
|             return False |             return False | ||||||
|         else: |         else: | ||||||
|             return True |             return True | ||||||
| @@ -159,7 +182,7 @@ class _HostReconnectionHandler(_ReconnectionHandler): | |||||||
|         self.host.monitor.reset() |         self.host.monitor.reset() | ||||||
|  |  | ||||||
|     def on_exception(self, exc, next_delay): |     def on_exception(self, exc, next_delay): | ||||||
|         if isinstance(exc, AuthenticationException): |         if isinstance(exc, AuthenticationFailed): | ||||||
|             return False |             return False | ||||||
|         else: |         else: | ||||||
|             log.warn("Error attempting to reconnect to %s: %s", self.host, exc) |             log.warn("Error attempting to reconnect to %s: %s", self.host, exc) | ||||||
| @@ -168,8 +191,17 @@ class _HostReconnectionHandler(_ReconnectionHandler): | |||||||
|  |  | ||||||
|  |  | ||||||
| class HealthMonitor(object): | class HealthMonitor(object): | ||||||
|  |     """ | ||||||
|  |     Monitors whether a particular host is marked as up or down. | ||||||
|  |     This class is primarily intended for internal use, although | ||||||
|  |     applications may find it useful to check whether a given node | ||||||
|  |     is up or down. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     is_up = True |     is_up = True | ||||||
|  |     """ | ||||||
|  |     A boolean representing the current state of the node. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     def __init__(self, conviction_policy): |     def __init__(self, conviction_policy): | ||||||
|         self._conviction_policy = conviction_policy |         self._conviction_policy = conviction_policy | ||||||
| @@ -352,7 +384,7 @@ class HostConnectionPool(object): | |||||||
|             if self.host.monitor.signal_connection_failure(exc): |             if self.host.monitor.signal_connection_failure(exc): | ||||||
|                 self.shutdown() |                 self.shutdown() | ||||||
|             return False |             return False | ||||||
|         except AuthenticationException: |         except AuthenticationFailed: | ||||||
|             with self._lock: |             with self._lock: | ||||||
|                 self.open_count -= 1 |                 self.open_count -= 1 | ||||||
|             return False |             return False | ||||||
|   | |||||||
| @@ -17,3 +17,12 @@ | |||||||
|  |  | ||||||
| .. autoexception:: WriteTimeout() | .. autoexception:: WriteTimeout() | ||||||
|    :members: |    :members: | ||||||
|  |  | ||||||
|  | .. autoexception:: InvalidRequest() | ||||||
|  |    :members: | ||||||
|  |  | ||||||
|  | .. autoexception:: Unauthorized() | ||||||
|  |    :members: | ||||||
|  |  | ||||||
|  | .. autoexception:: AuthenticationFailed() | ||||||
|  |    :members: | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| ``cassandra.cluster`` | ``cassandra.cluster`` - Clusters and Sessions | ||||||
| ===================== | ============================================= | ||||||
|  |  | ||||||
| .. module:: cassandra.cluster | .. module:: cassandra.cluster | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								docs/api/cassandra/connection.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/cassandra/connection.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | ``cassandra.connection`` - Low Level Connection Info | ||||||
|  | ==================================================== | ||||||
|  |  | ||||||
|  | .. module:: cassandra.connection | ||||||
|  |  | ||||||
|  | .. autoexception:: ConnectionException () | ||||||
|  | .. autoexception:: ConnectionBusy () | ||||||
|  | .. autoexception:: ProtocolError () | ||||||
							
								
								
									
										7
									
								
								docs/api/cassandra/io/asyncorereactor.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/api/cassandra/io/asyncorereactor.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | ``cassandra.io.asyncorereactor`` - ``asyncore`` Event Loop | ||||||
|  | ========================================================== | ||||||
|  |  | ||||||
|  | .. module:: cassandra.io.asyncorereactor | ||||||
|  |  | ||||||
|  | .. autoclass:: AsyncoreConnection | ||||||
|  |    :members: | ||||||
							
								
								
									
										6
									
								
								docs/api/cassandra/io/libevreactor.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								docs/api/cassandra/io/libevreactor.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | ``cassandra.io.libevreactor`` - ``libev`` Event Loop | ||||||
|  | ==================================================== | ||||||
|  |  | ||||||
|  | .. module:: cassandra.io.libevreactor | ||||||
|  |  | ||||||
|  | .. autoclass:: LibevConnection | ||||||
							
								
								
									
										35
									
								
								docs/api/cassandra/metadata.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								docs/api/cassandra/metadata.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | ``cassandra.metadata`` - Schema and Ring Topology | ||||||
|  | ================================================= | ||||||
|  |  | ||||||
|  | .. module:: cassandra.metadata | ||||||
|  |  | ||||||
|  | .. autoclass:: Metadata () | ||||||
|  |    :members: | ||||||
|  |    :exclude-members: rebuild_schema, rebuild_token_map, add_host, remove_host, get_host | ||||||
|  |  | ||||||
|  | .. autoclass:: KeyspaceMetadata () | ||||||
|  |    :members: | ||||||
|  |  | ||||||
|  | .. autoclass:: TableMetadata () | ||||||
|  |    :members: | ||||||
|  |  | ||||||
|  | .. autoclass:: ColumnMetadata () | ||||||
|  |    :members: | ||||||
|  |  | ||||||
|  | .. autoclass:: IndexMetadata () | ||||||
|  |    :members: | ||||||
|  |  | ||||||
|  | .. autoclass:: TokenMap | ||||||
|  |    :members: | ||||||
|  |  | ||||||
|  | .. autoclass:: Token | ||||||
|  |    :members: | ||||||
|  |  | ||||||
|  | .. autoclass:: Murmur3Token | ||||||
|  |    :members: | ||||||
|  |  | ||||||
|  | .. autoclass:: MD5Token | ||||||
|  |    :members: | ||||||
|  |  | ||||||
|  | .. autoclass:: BytesToken | ||||||
|  |    :members: | ||||||
							
								
								
									
										11
									
								
								docs/api/cassandra/pool.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								docs/api/cassandra/pool.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | ``cassandra.pool`` - Hosts and Connection Pools | ||||||
|  | =============================================== | ||||||
|  |  | ||||||
|  | .. automodule:: cassandra.pool | ||||||
|  |  | ||||||
|  | .. autoclass:: Host () | ||||||
|  |    :members: | ||||||
|  |    :exclude-members: set_location_info, get_and_set_reconnection_handler | ||||||
|  |  | ||||||
|  | .. autoclass:: HealthMonitor () | ||||||
|  |    :members: | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| ``cassandra.query`` | ``cassandra.query`` - Prepared Statements and Query Policies | ||||||
| =================== | ============================================================ | ||||||
|  |  | ||||||
| .. module:: cassandra.query | .. module:: cassandra.query | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,4 +15,4 @@ Cassandra Modules | |||||||
|    cassandra/pool |    cassandra/pool | ||||||
|    cassandra/connection |    cassandra/connection | ||||||
|    cassandra/io/asyncorereactor |    cassandra/io/asyncorereactor | ||||||
|    cassandra/io/pyevreactor |    cassandra/io/libevreactor | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ from threading import Thread, Event | |||||||
| from cassandra import ConsistencyLevel | from cassandra import ConsistencyLevel | ||||||
| from cassandra.decoder import QueryMessage | from cassandra.decoder import QueryMessage | ||||||
| from cassandra.io.asyncorereactor import AsyncoreConnection | from cassandra.io.asyncorereactor import AsyncoreConnection | ||||||
| from cassandra.io.pyevreactor import PyevConnection | from cassandra.io.libevreactor import LibevConnection | ||||||
|  |  | ||||||
| class ConnectionTest(object): | class ConnectionTest(object): | ||||||
|  |  | ||||||
| @@ -164,6 +164,6 @@ class AsyncoreConnectionTest(ConnectionTest, unittest.TestCase): | |||||||
|     klass = AsyncoreConnection |     klass = AsyncoreConnection | ||||||
|  |  | ||||||
|  |  | ||||||
| class PyevConnectionTest(ConnectionTest, unittest.TestCase): | class LibevConnectionTest(ConnectionTest, unittest.TestCase): | ||||||
|  |  | ||||||
|     klass = PyevConnection |     klass = LibevConnection | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ from cassandra.connection import (PROTOCOL_VERSION, | |||||||
|                                   HEADER_DIRECTION_TO_CLIENT, |                                   HEADER_DIRECTION_TO_CLIENT, | ||||||
|                                   ProtocolError, |                                   ProtocolError, | ||||||
|                                   ConnectionException) |                                   ConnectionException) | ||||||
| from cassandra.io.pyevreactor import PyevConnection | from cassandra.io.libevreactor import LibevConnection | ||||||
| from cassandra.decoder import (write_stringmultimap, write_int, write_string, | from cassandra.decoder import (write_stringmultimap, write_int, write_string, | ||||||
|                                SupportedMessage, ReadyMessage, ServerError) |                                SupportedMessage, ReadyMessage, ServerError) | ||||||
| from cassandra.marshal import uint8_pack, uint32_pack | from cassandra.marshal import uint8_pack, uint32_pack | ||||||
| @@ -17,10 +17,10 @@ from cassandra.marshal import uint8_pack, uint32_pack | |||||||
| @patch('socket.socket') | @patch('socket.socket') | ||||||
| @patch('pyev.Io') | @patch('pyev.Io') | ||||||
| @patch('cassandra.io.asyncorereactor._start_loop') | @patch('cassandra.io.asyncorereactor._start_loop') | ||||||
| class PyevConnectionTest(unittest.TestCase): | class LibevConnectionTest(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
|     def make_connection(self): |     def make_connection(self): | ||||||
|         c = PyevConnection('1.2.3.4') |         c = LibevConnection('1.2.3.4') | ||||||
|         c._socket = Mock() |         c._socket = Mock() | ||||||
|         c._socket.send.side_effect = lambda x: len(x) |         c._socket.send.side_effect = lambda x: len(x) | ||||||
|         return c |         return c | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| from StringIO import StringIO | from StringIO import StringIO | ||||||
| import unittest | import unittest | ||||||
|  |  | ||||||
| from mock import patch, Mock, ANY | from mock import Mock, ANY | ||||||
|  |  | ||||||
| from cassandra.connection import (Connection, PROTOCOL_VERSION, | from cassandra.connection import (Connection, PROTOCOL_VERSION, | ||||||
|                                   HEADER_DIRECTION_TO_CLIENT, |                                   HEADER_DIRECTION_TO_CLIENT, | ||||||
| @@ -10,9 +10,6 @@ from cassandra.decoder import (write_stringmultimap, write_int, write_string, | |||||||
|                                SupportedMessage) |                                SupportedMessage) | ||||||
| from cassandra.marshal import uint8_pack, uint32_pack | from cassandra.marshal import uint8_pack, uint32_pack | ||||||
|  |  | ||||||
| @patch('socket.socket') |  | ||||||
| @patch('pyev.Io') |  | ||||||
| @patch('cassandra.io.asyncorereactor._start_loop') |  | ||||||
| class ConnectionTest(unittest.TestCase): | class ConnectionTest(unittest.TestCase): | ||||||
|  |  | ||||||
|     def make_connection(self): |     def make_connection(self): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Tyler Hobbs
					Tyler Hobbs