diff --git a/cassandra/cluster.py b/cassandra/cluster.py index b1961296..fa5defd1 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -179,12 +179,29 @@ class Cluster(object): protocol_version = 2 """ - The version of the native protocol to use. The protocol version 2 - add support for lightweight transactions, batch operations, and - automatic query paging, but is only supported by Cassandra 2.0+. When - working with Cassandra 1.2, this must be set to 1. You can also set - this to 1 when working with Cassandra 2.0+, but features that require - the version 2 protocol will not be enabled. + The version of the native protocol to use. + + Version 2 of the native protocol adds support for lightweight transactions, + batch operations, and automatic query paging. The v2 protocol is + supported by Cassandra 2.0+. + + Version 3 of the native protocol adds support for protocol-level + client-side timestamps (see :attr:`.Session.use_client_timestamp`), + serial consistency levels for :class:`~.BatchStatement`, and an + improved connection pool. + + The following table describes the native protocol versions that + are supported by each version of Cassandra: + + +-------------------+-------------------+ + | Cassandra Version | Protocol Versions | + +===================+===================+ + | 1.2 | 1 | + +-------------------+-------------------+ + | 2.0 | 1, 2 | + +-------------------+-------------------+ + | 2.1 | 1, 2, 3 | + +-------------------+-------------------+ """ compression = True @@ -320,8 +337,8 @@ class Cluster(object): * :class:`cassandra.io.asyncorereactor.AsyncoreConnection` * :class:`cassandra.io.libevreactor.LibevConnection` - * :class:`cassandra.io.libevreactor.GeventConnection` (requires monkey-patching) - * :class:`cassandra.io.libevreactor.TwistedConnection` + * :class:`cassandra.io.geventreactor.GeventConnection` (requires monkey-patching) + * :class:`cassandra.io.twistedreactor.TwistedConnection` By default, ``AsyncoreConnection`` will be used, which uses the ``asyncore`` module in the Python standard library. The @@ -1191,6 +1208,8 @@ class Session(object): session.execute("CREATE TABLE mytable (k int PRIMARY KEY, col tuple)") session.execute("INSERT INTO mytable (k, col) VALUES (%s, %s)", [0, (123, 'abc')]) + + .. versionadded:: 2.1.0 """ _lock = None @@ -1614,6 +1633,8 @@ class Session(object): class UserTypeDoesNotExist(Exception): """ An attempt was made to use a user-defined type that does not exist. + + .. versionadded:: 2.1.0 """ pass diff --git a/cassandra/metadata.py b/cassandra/metadata.py index 8aa77c38..6d4a759d 100644 --- a/cassandra/metadata.py +++ b/cassandra/metadata.py @@ -458,6 +458,10 @@ class SimpleStrategy(ReplicationStrategy): return replica_map def export_for_schema(self): + """ + Returns a string version of these replication options which are + suitable for use in a CREATE KEYSPACE statement. + """ return "{'class': 'SimpleStrategy', 'replication_factor': '%d'}" \ % (self.replication_factor,) @@ -537,6 +541,10 @@ class NetworkTopologyStrategy(ReplicationStrategy): return replica_map def export_for_schema(self): + """ + Returns a string version of these replication options which are + suitable for use in a CREATE KEYSPACE statement. + """ ret = "{'class': 'NetworkTopologyStrategy'" for dc, repl_factor in self.dc_replication_factors: ret += ", '%s': '%d'" % (dc, repl_factor) @@ -557,6 +565,10 @@ class LocalStrategy(ReplicationStrategy): return {} def export_for_schema(self): + """ + Returns a string version of these replication options which are + suitable for use in a CREATE KEYSPACE statement. + """ return "{'class': 'LocalStrategy'}" def __eq__(self, other): @@ -588,6 +600,11 @@ class KeyspaceMetadata(object): """ user_types = None + """ + A map from user-defined type names to instances of :class:`~cassandra.metadata..UserType`. + + .. versionadded:: 2.1.0 + """ def __init__(self, name, durable_writes, strategy_class, strategy_options): self.name = name @@ -597,9 +614,17 @@ class KeyspaceMetadata(object): self.user_types = {} def export_as_string(self): + """ + Returns a CQL query string that can be used to recreate the entire keyspace, + including user-defined types and tables. + """ return "\n\n".join([self.as_cql_query()] + self.user_type_strings() + [t.export_as_string() for t in self.tables.values()]) def as_cql_query(self): + """ + Returns a CQL query string that can be used to recreate just this keyspace, + not including user-defined types and tables. + """ ret = "CREATE KEYSPACE %s WITH replication = %s " % ( protect_name(self.name), self.replication_strategy.export_for_schema()) @@ -950,6 +975,10 @@ class ColumnMetadata(object): """ The string name of this column. """ data_type = None + """ + The data type for the column in the form of an instance of one of + the type classes in :mod:`cassandra.cqltypes`. + """ index = None """ diff --git a/cassandra/query.py b/cassandra/query.py index 3c0fb909..ffb5f8c2 100644 --- a/cassandra/query.py +++ b/cassandra/query.py @@ -572,11 +572,17 @@ class BatchStatement(Statement): :attr:`.BatchType.LOGGED`. """ + serial_consistency_level = None + """ + The same as :attr:`.Statement.serial_consistency_level`, but is only + supported when using protocol version 3 or higher. + """ + _statements_and_parameters = None _session = None def __init__(self, batch_type=BatchType.LOGGED, retry_policy=None, - consistency_level=None, session=None): + consistency_level=None, serial_consistency_level=None, session=None): """ `batch_type` specifies The :class:`.BatchType` for the batch operation. Defaults to :attr:`.BatchType.LOGGED`. @@ -609,11 +615,15 @@ class BatchStatement(Statement): session.execute(batch) .. versionadded:: 2.0.0 + + .. versionchanged:: 2.1.0 + Added `serial_consistency_level` as a parameter """ self.batch_type = batch_type self._statements_and_parameters = [] self._session = session - Statement.__init__(self, retry_policy=retry_policy, consistency_level=consistency_level) + Statement.__init__(self, retry_policy=retry_policy, consistency_level=consistency_level, + serial_consistency_level=serial_consistency_level) def add(self, statement, parameters=None): """ diff --git a/docs/api/cassandra/io/geventreactor.rst b/docs/api/cassandra/io/geventreactor.rst new file mode 100644 index 00000000..603affe1 --- /dev/null +++ b/docs/api/cassandra/io/geventreactor.rst @@ -0,0 +1,7 @@ +``cassandra.io.geventreactor`` - ``gevent``-compatible Event Loop +================================================================= + +.. module:: cassandra.io.geventreactor + +.. autoclass:: GeventConnection + :members: diff --git a/docs/api/cassandra/io/twistedreactor.rst b/docs/api/cassandra/io/twistedreactor.rst new file mode 100644 index 00000000..24e93bd4 --- /dev/null +++ b/docs/api/cassandra/io/twistedreactor.rst @@ -0,0 +1,9 @@ +``cassandra.io.twistedreactor`` - Twisted Event Loop +==================================================== + +.. module:: cassandra.io.twistedreactor + +.. class:: TwistedConnection + + An implementation of :class:`~cassandra.io.connection.Connection` that uses + Twisted's reactor as its event loop. diff --git a/docs/api/index.rst b/docs/api/index.rst index fc083c6a..7db7c7ee 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -18,3 +18,5 @@ API Documentation cassandra/connection cassandra/io/asyncorereactor cassandra/io/libevreactor + cassandra/io/geventreactor + cassandra/io/twistedreactor diff --git a/docs/getting_started.rst b/docs/getting_started.rst index d82098fa..65969f88 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -190,6 +190,8 @@ Only data values should be supplied this way. Other items, such as keyspaces, table names, and column names should be set ahead of time (typically using normal string formatting). +.. _type-conversions: + Type Conversions ^^^^^^^^^^^^^^^^ For non-prepared statements, Python types are cast to CQL literals in the @@ -360,9 +362,7 @@ handles re-preparing against new nodes and restarted nodes when necessary. Note that the placeholders for prepared statements are ``?`` characters. This is different than for simple, non-prepared statements (although future versions -of the driver may use the same placeholders for both). Cassandra 2.0 added -support for named placeholders; the 1.0 version of the driver does not support -them, but the 2.0 version will. +of the driver may use the same placeholders for both). Setting a Consistency Level with Prepared Statements ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 78af6014..4102a39e 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -1,6 +1,95 @@ Upgrading ========= +.. toctree:: + :maxdepth: 1 + +Upgrading to 2.1 from 2.0 +------------------------- +Version 2.1 of the Datastax python driver for Apache Cassandra +adds support for Cassandra 2.1 and version 3 of the native protocol. + +Cassandra 1.2, 2.0, and 2.1 are all supported. However, 1.2 only +supports protocol version 1, and 2.0 only supports versions 1 and +2, so some features may not be available. + +Using the v3 Native Protocol +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +By default, the driver will attempt to use version 2 of the +native protocol. To use version 3, you must explicitly +set the :attr:`~.Cluster.protocol_version`: + +.. code-block:: python + + from cassandra.cluster import Cluster + + cluster = Cluster(protocol_version=3) + +Note that protocol version 3 is only supported by Cassandra 2.1+. + +In future releases, the driver may default to using protocol version +3. + +Working with User-Defined Types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Cassandra 2.1 introduced the ability to define new types:: + + USE KEYSPACE mykeyspace; + + CREATE TYPE address (street text, city text, zip int); + +The driver generally expects you to use instances of a specific +class to represent column values of this type. You can let the +driver know what class to use with :meth:`.Cluster.register_user_type`: + +.. code-block:: python + + cluster = Cluster() + + class Address(object): + + def __init__(self, street, city, zipcode): + self.street = street + self.city = text + self.zipcode = zipcode + + cluster.register_user_type('mykeyspace', 'address', Address) + +When inserting data for ``address`` columns, you should pass in +instances of ``Address``. When querying data, ``address`` column +values will be instances of ``Address``. + +If no class is registered for a user-defined type, query results +will use a ``namedtuple`` class and data may only be inserted +though prepared statements. + +Customizing Encoders for Non-prepared Statements +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Starting with version 2.1 of the driver, it is possible to customize +how python types are converted to CQL literals when working with +non-prepared statements. This is done on a per-:class:`~.Session` +basis through :attr:`.Session.encoder`: + +.. code-block:: python + + cluster = Cluster() + session = cluster.connect() + session.encoder.mapping[tuple] = session.encoder.cql_encode_tuple + +See :ref:`type-conversions` for the table of default CQL literal conversions. + +Using Client-Side Protocol-Level Timestamps +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +With version 3 of the native protocol, timestamps may be supplied by the +client at the protocol level. (Normally, if they are not specified within +the CQL query itself, a timestamp is generated server-side.) + +When :attr:`~.Cluster.protocol_version` is set to 3 or higher, the driver +will automatically use client-side timestamps with microsecond precision +unless :attr:`.Session.use_client_timestamp` is changed to :const:`False`. +If a timestamp is specified within the CQL query, it will override the +timestamp generated by the driver. + Upgrading to 2.0 from 1.x ------------------------- Version 2.0 of the DataStax python driver for Apache Cassandra diff --git a/example.py b/example.py index acb7b864..e068db98 100644 --- a/example.py +++ b/example.py @@ -33,14 +33,9 @@ def main(): cluster = Cluster(['127.0.0.1']) session = cluster.connect() - rows = session.execute("SELECT keyspace_name FROM system.schema_keyspaces") - if KEYSPACE in [row[0] for row in rows]: - log.info("dropping existing keyspace...") - session.execute("DROP KEYSPACE " + KEYSPACE) - log.info("creating keyspace...") session.execute(""" - CREATE KEYSPACE %s + CREATE KEYSPACE IF NOT EXISTS %s WITH replication = { 'class': 'SimpleStrategy', 'replication_factor': '2' } """ % KEYSPACE) @@ -49,7 +44,7 @@ def main(): log.info("creating table...") session.execute(""" - CREATE TABLE mytable ( + CREATE TABLE IF NOT EXISTS mytable ( thekey text, col1 text, col2 text, diff --git a/setup.py b/setup.py index ecbdc43f..f49c9aae 100644 --- a/setup.py +++ b/setup.py @@ -96,7 +96,7 @@ class DocCommand(Command): print("") print("Documentation step '%s' performed, results here:" % mode) - print(" %s/" % path) + print(" file://%s/%s/index.html" % (os.path.dirname(os.path.realpath(__file__)), path)) class BuildFailed(Exception):