cqlengine integration: split docs into code, divide api docs

Still more to be done in object_mapper.rst cqlengine
This commit is contained in:
Adam Holmberg
2015-02-06 17:03:53 -06:00
parent c13f047f45
commit 5a22c2d7e4
16 changed files with 1059 additions and 925 deletions

View File

@@ -92,6 +92,57 @@ class Column(object):
instance_counter = 0
primary_key = False
"""
bool flag, indicates this column is a primary key. The first primary key defined
on a model is the partition key (unless partition keys are set), all others are cluster keys
"""
partition_key = False
"""
indicates that this column should be the partition key, defining
more than one partition key column creates a compound partition key
"""
index = False
"""
bool flag, indicates an index should be created for this column
"""
db_field = False
"""
the fieldname this field will map to in the database
"""
default = False
"""
the default value, can be a value or a callable (no args)
"""
required = False
"""
boolean, is the field required? Model validation will raise and
exception if required is set to True and there is a None value assigned
"""
clustering_order = False
"""
only applicable on clustering keys (primary keys that are not partition keys)
determines the order that the clustering keys are sorted on disk
"""
polymorphic_key = False
"""
boolean, if set to True, this column will be used for saving and loading instances
of polymorphic tables
"""
static = False
"""
boolean, if set to True, this is a static column, with a single value per partition
"""
def __init__(self,
primary_key=False,
partition_key=False,
@@ -102,21 +153,6 @@ class Column(object):
clustering_order=None,
polymorphic_key=False,
static=False):
"""
:param primary_key: bool flag, indicates this column is a primary key. The first primary key defined
on a model is the partition key (unless partition keys are set), all others are cluster keys
:param partition_key: indicates that this column should be the partition key, defining
more than one partition key column creates a compound partition key
:param index: bool flag, indicates an index should be created for this column
:param db_field: the fieldname this field will map to in the database
:param default: the default value, can be a value or a callable (no args)
:param required: boolean, is the field required? Model validation will raise and
exception if required is set to True and there is a None value assigned
:param clustering_order: only applicable on clustering keys (primary keys that are not partition keys)
determines the order that the clustering keys are sorted on disk
:param polymorphic_key: boolean, if set to True, this column will be used for saving and loading instances
of polymorphic tables
"""
self.partition_key = partition_key
self.primary_key = partition_key or primary_key
self.index = index
@@ -216,6 +252,9 @@ class Column(object):
class Blob(Column):
"""
Stores a raw binary value
"""
db_type = 'blob'
def to_database(self, value):
@@ -233,15 +272,36 @@ class Blob(Column):
Bytes = Blob
class Ascii(Column):
"""
Stores a US-ASCII character string
"""
db_type = 'ascii'
class Inet(Column):
"""
Stores an IP address in IPv4 or IPv6 format
"""
db_type = 'inet'
class Text(Column):
"""
Stores a UTF-8 encoded string
"""
db_type = 'text'
min_length = None
"""
Sets the minimum length of this string, for validation purposes.
Defaults to 1 if this is a ``required`` column. Otherwise, None.
"""
max_length = None
"""
Sets the maximum length of this string, for validation purposes.
"""
def __init__(self, *args, **kwargs):
self.min_length = kwargs.pop('min_length', 1 if kwargs.get('required', False) else None)
self.max_length = kwargs.pop('max_length', None)
@@ -262,6 +322,10 @@ class Text(Column):
class Integer(Column):
"""
Stores a 32-bit signed integer value
"""
db_type = 'int'
def validate(self, value):
@@ -280,10 +344,16 @@ class Integer(Column):
class BigInt(Integer):
"""
Stores a 64-bit signed long value
"""
db_type = 'bigint'
class VarInt(Column):
"""
Stores an arbitrary-precision integer
"""
db_type = 'varint'
def validate(self, value):
@@ -311,6 +381,9 @@ class CounterValueManager(BaseValueManager):
class Counter(Integer):
"""
Stores a counter that can be inremented and decremented
"""
db_type = 'counter'
value_manager = CounterValueManager
@@ -330,6 +403,9 @@ class Counter(Integer):
class DateTime(Column):
"""
Stores a datetime value
"""
db_type = 'timestamp'
def to_python(self, value):
@@ -358,6 +434,12 @@ class DateTime(Column):
class Date(Column):
"""
*Note: this type is overloaded, and will likely be changed or removed to accommodate distinct date type
in a future version*
Stores a date value, with no time-of-day
"""
db_type = 'timestamp'
def to_python(self, value):
@@ -384,7 +466,7 @@ class Date(Column):
class UUID(Column):
"""
Type 1 or 4 UUID
Stores a type 1 or 4 UUID
"""
db_type = 'uuid'
@@ -451,6 +533,9 @@ class TimeUUID(UUID):
class Boolean(Column):
"""
Stores a boolean True or False value
"""
db_type = 'boolean'
def validate(self, value):
@@ -467,6 +552,9 @@ class Boolean(Column):
class Float(Column):
"""
Stores a 32-bit floating point value
"""
db_type = 'double'
def __init__(self, double_precision=True, **kwargs):
@@ -489,6 +577,9 @@ class Float(Column):
class Decimal(Column):
"""
Stores a variable precision decimal value
"""
db_type = 'decimal'
def validate(self, value):

View File

@@ -274,14 +274,12 @@ class BaseModel(object):
# _len is lazily created by __len__
# table names will be generated automatically from it's model
# however, you can also define them manually here
__table_name__ = None
# the keyspace for this model
__keyspace__ = None
# polymorphism options
__default_ttl__ = None
__polymorphic_key__ = None
# compaction options
@@ -304,9 +302,9 @@ class BaseModel(object):
__queryset__ = ModelQuerySet
__dmlquery__ = DMLQuery
__default_ttl__ = None # default ttl value to use
__consistency__ = None # can be set per query
# Additional table properties
__bloom_filter_fp_chance__ = None
__caching__ = None
@@ -429,7 +427,9 @@ class BaseModel(object):
@classmethod
def _get_keyspace(cls):
""" Returns the manual keyspace, if set, otherwise the default keyspace """
"""
Returns the manual keyspace, if set, otherwise the default keyspace
"""
return cls.__keyspace__ or DEFAULT_KEYSPACE
@classmethod
@@ -489,7 +489,9 @@ class BaseModel(object):
return '{}.{}'.format(cls._get_keyspace(), cf_name)
def validate(self):
""" Cleans and validates the field values """
"""
Cleans and validates the field values
"""
for name, col in self._columns.items():
v = getattr(self, name)
if v is None and not self._values[name].explicit and col.has_default:
@@ -521,7 +523,9 @@ class BaseModel(object):
return setattr(self, key, val)
def __len__(self):
""" Returns the number of columns defined on that model. """
"""
Returns the number of columns defined on that model.
"""
try:
return self._len
except:
@@ -529,15 +533,15 @@ class BaseModel(object):
return self._len
def keys(self):
""" Returns list of column's IDs. """
""" Returns a list of column IDs. """
return [k for k in self]
def values(self):
""" Returns list of column's values. """
""" Returns list of column values. """
return [self[k] for k in self]
def items(self):
""" Returns a list of columns's IDs/values. """
""" Returns a list of column ID/value tuples. """
return [(k, self[k]) for k in self]
def _as_dict(self):
@@ -549,6 +553,13 @@ class BaseModel(object):
@classmethod
def create(cls, **kwargs):
"""
Create an instance of this model in the database.
Takes the model column values as keyword arguments.
Returns the instance.
"""
extra_columns = set(kwargs.keys()) - set(cls._columns.keys())
if extra_columns:
raise ValidationError("Incorrect columns passed: {}".format(extra_columns))
@@ -556,25 +567,52 @@ class BaseModel(object):
@classmethod
def all(cls):
"""
Returns a queryset representing all stored objects
This is a pass-through to the model objects().all()
"""
return cls.objects.all()
@classmethod
def filter(cls, *args, **kwargs):
# if kwargs.values().count(None):
# raise CQLEngineException("Cannot pass None as a filter")
"""
Returns a queryset based on filter parameters.
This is a pass-through to the model objects().:method:`~cqlengine.queries.filter`.
"""
return cls.objects.filter(*args, **kwargs)
@classmethod
def get(cls, *args, **kwargs):
"""
Returns a single object based on the passed filter constraints.
This is a pass-through to the model objects().:method:`~cqlengine.queries.get`.
"""
return cls.objects.get(*args, **kwargs)
def timeout(self, timeout):
"""
Sets a timeout for use in :meth:`~.save`, :meth:`~.update`, and :meth:`~.delete`
operations
"""
assert self._batch is None, 'Setting both timeout and batch is not supported'
self._timeout = timeout
return self
def save(self):
"""
Saves an object to the database.
.. code-block:: python
#create a person instance
person = Person(first_name='Kimberly', last_name='Eggleston')
#saves it to Cassandra
person.save()
"""
# handle polymorphic models
if self._is_polymorphic:
if self._is_polymorphic_base:
@@ -604,6 +642,15 @@ class BaseModel(object):
return self
def update(self, **values):
"""
Performs an update on the model instance. You can pass in values to set on the model
for updating, or you can call without values to execute an update against any modified
fields. If no fields on the model have been modified since loading, no query will be
performed. Model validation is performed normally.
It is possible to do a blind update, that is, to update a field without having first selected the object out of the database.
See :ref:`Blind Updates <blind_updates>`
"""
for k, v in values.items():
col = self._columns.get(k)
@@ -644,7 +691,9 @@ class BaseModel(object):
return self
def delete(self):
""" Deletes this instance """
"""
Deletes the object from the database
"""
self.__dmlquery__(self.__class__, self,
batch=self._batch,
timestamp=self._timestamp,
@@ -652,7 +701,9 @@ class BaseModel(object):
timeout=self._timeout).delete()
def get_changed_columns(self):
""" returns a list of the columns that have been updated since instantiation or save """
"""
Returns a list of the columns that have been updated since instantiation or save
"""
return [k for k,v in self._values.items() if v.changed]
@classmethod
@@ -668,12 +719,9 @@ class BaseModel(object):
batch = hybrid_classmethod(_class_batch, _inst_batch)
class ModelMetaClass(type):
def __new__(cls, name, bases, attrs):
"""
"""
#move column definitions into columns dict
#and set default column names
column_dict = OrderedDict()
@@ -839,11 +887,30 @@ import six
@six.add_metaclass(ModelMetaClass)
class Model(BaseModel):
"""
the db name for the column family can be set as the attribute db_name, or
it will be generated from the class name
"""
__abstract__ = True
# __metaclass__ = ModelMetaClass
"""
*Optional.* Indicates that this model is only intended to be used as a base class for other models.
You can't create tables for abstract models, but checks around schema validity are skipped during class construction.
"""
__table_name__ = None
"""
*Optional.* Sets the name of the CQL table for this model. If left blank, the table name will be the name of the model, with it's module name as it's prefix. Manually defined table names are not inherited.
"""
__keyspace__ = None
"""
Sets the name of the keyspace used by this model.
"""
__default_ttl__ = None
"""
*Optional* The default ttl used by this model.
This can be overridden by using the :meth:`~.ttl` method.
"""
__polymorphic_key__ = None
"""
*Optional* Specifies a value for the polymorphic key when using model inheritance.
"""

View File

@@ -379,9 +379,9 @@ class AbstractQuerySet(object):
def batch(self, batch_obj):
"""
Adds a batch query to the mix
:param batch_obj:
:return:
Set a batch object to run the query on.
Note: running a select query with a batch object will raise an exception
"""
if batch_obj is not None and not isinstance(batch_obj, BatchQuery):
raise CQLEngineException('batch_obj must be a BatchQuery instance or None')
@@ -396,9 +396,25 @@ class AbstractQuerySet(object):
return None
def all(self):
"""
Returns a queryset matching all rows
.. code-block:: python
for user in User.objects().all():
print(user)
"""
return copy.deepcopy(self)
def consistency(self, consistency):
"""
Sets the consistency level for the operation. See :class:`.ConsistencyLevel`.
.. code-block:: python
for user in User.objects(id=3).consistency(CL.ONE):
print(user)
"""
clone = copy.deepcopy(self)
clone._consistency = consistency
return clone
@@ -464,9 +480,9 @@ class AbstractQuerySet(object):
"""
Adds WHERE arguments to the queryset, returning a new queryset
#TODO: show examples
See :ref:`retrieving-objects-with-filters`
:rtype: AbstractQuerySet
Returns a QuerySet filtered on the keyword arguments
"""
#add arguments to the where clause filters
if len([x for x in kwargs.values() if x is None]):
@@ -524,8 +540,17 @@ class AbstractQuerySet(object):
"""
Returns a single instance matching this query, optionally with additional filter kwargs.
A DoesNotExistError will be raised if there are no rows matching the query
A MultipleObjectsFoundError will be raised if there is more than one row matching the queyr
See :ref:`retrieving-objects-with-filters`
Returns a single object matching the QuerySet.
.. code-block:: python
user = User.get(id=1)
If no objects are matched, a :class:`~.DoesNotExist` exception is raised.
If more than one object is found, a :class:`~.MultipleObjectsReturned` exception is raised.
"""
if args or kwargs:
return self.filter(*args, **kwargs).get()
@@ -547,10 +572,34 @@ class AbstractQuerySet(object):
def order_by(self, *colnames):
"""
orders the result set.
ordering can only use clustering columns.
Sets the column(s) to be used for ordering
Default order is ascending, prepend a '-' to the column name for descending
Default order is ascending, prepend a '-' to any column name for descending
*Note: column names must be a clustering key*
.. code-block:: python
from uuid import uuid1,uuid4
class Comment(Model):
photo_id = UUID(primary_key=True)
comment_id = TimeUUID(primary_key=True, default=uuid1) # second primary key component is a clustering key
comment = Text()
sync_table(Comment)
u = uuid4()
for x in range(5):
Comment.create(photo_id=u, comment="test %d" % x)
print("Normal")
for comment in Comment.objects(photo_id=u):
print comment.comment_id
print("Reversed")
for comment in Comment.objects(photo_id=u).order_by("-comment_id"):
print comment.comment_id
"""
if len(colnames) == 0:
clone = copy.deepcopy(self)
@@ -566,7 +615,9 @@ class AbstractQuerySet(object):
return clone
def count(self):
""" Returns the number of rows matched by this query """
"""
Returns the number of rows matched by this query
"""
if self._batch:
raise CQLEngineException("Only inserts, updates, and deletes are available in batch mode")
@@ -580,8 +631,14 @@ class AbstractQuerySet(object):
def limit(self, v):
"""
Sets the limit on the number of results returned
CQL has a default limit of 10,000
Limits the number of results returned by Cassandra.
*Note that CQL's default limit is 10,000, so all queries without a limit set explicitly will have an implicit limit of 10,000*
.. code-block:: python
for user in User.objects().limit(100):
print(user)
"""
if not (v is None or isinstance(v, six.integer_types)):
raise TypeError
@@ -597,8 +654,7 @@ class AbstractQuerySet(object):
def allow_filtering(self):
"""
Enables the unwise practive of querying on a clustering
key without also defining a partition key
Enables the (usually) unwise practive of querying on a clustering key without also defining a partition key
"""
clone = copy.deepcopy(self)
clone._allow_filtering = True
@@ -700,7 +756,6 @@ class SimpleQuerySet(AbstractQuerySet):
class ModelQuerySet(AbstractQuerySet):
"""
"""
def _validate_select_where(self):
""" Checks that a filterset will not create invalid select statement """
@@ -774,11 +829,19 @@ class ModelQuerySet(AbstractQuerySet):
return clone
def ttl(self, ttl):
"""
Sets the ttl (in seconds) for modified data.
*Note that running a select query with a ttl value will raise an exception*
"""
clone = copy.deepcopy(self)
clone._ttl = ttl
return clone
def timestamp(self, timestamp):
"""
Allows for custom timestamps to be saved with the record.
"""
clone = copy.deepcopy(self)
clone._timestamp = timestamp
return clone
@@ -791,7 +854,92 @@ class ModelQuerySet(AbstractQuerySet):
return clone
def update(self, **values):
""" Updates the rows in this queryset """
"""
Performs an update on the row selected by the queryset. Include values to update in the
update like so:
.. code-block:: python
Model.objects(key=n).update(value='x')
Passing in updates for columns which are not part of the model will raise a ValidationError.
Per column validation will be performed, but instance level validation will not
(i.e., `Model.validate` is not called). This is sometimes referred to as a blind update.
For example:
.. code-block:: python
class User(Model):
id = Integer(primary_key=True)
name = Text()
setup(["localhost"], "test")
sync_table(User)
u = User.create(id=1, name="jon")
User.objects(id=1).update(name="Steve")
# sets name to null
User.objects(id=1).update(name=None)
Also supported is blindly adding and removing elements from container columns,
without loading a model instance from Cassandra.
Using the syntax `.update(column_name={x, y, z})` will overwrite the contents of the container, like updating a
non container column. However, adding `__<operation>` to the end of the keyword arg, makes the update call add
or remove items from the collection, without overwriting then entire column.
Given the model below, here are the operations that can be performed on the different container columns:
.. code-block:: python
class Row(Model):
row_id = columns.Integer(primary_key=True)
set_column = columns.Set(Integer)
list_column = columns.List(Integer)
map_column = columns.Map(Integer, Integer)
:class:`~cqlengine.columns.Set`
- `add`: adds the elements of the given set to the column
- `remove`: removes the elements of the given set to the column
.. code-block:: python
# add elements to a set
Row.objects(row_id=5).update(set_column__add={6})
# remove elements to a set
Row.objects(row_id=5).update(set_column__remove={4})
:class:`~cqlengine.columns.List`
- `append`: appends the elements of the given list to the end of the column
- `prepend`: prepends the elements of the given list to the beginning of the column
.. code-block:: python
# append items to a list
Row.objects(row_id=5).update(list_column__append=[6, 7])
# prepend items to a list
Row.objects(row_id=5).update(list_column__prepend=[1, 2])
:class:`~cqlengine.columns.Map`
- `update`: adds the given keys/values to the columns, creating new entries if they didn't exist, and overwriting old ones if they did
.. code-block:: python
# add items to a map
Row.objects(row_id=5).update(map_column__update={1: 2, 3: 4})
"""
if not values:
return

View File

@@ -5,6 +5,8 @@
.. autoclass:: Cluster ([contact_points=('127.0.0.1',)][, port=9042][, executor_threads=2], **attr_kwargs)
Any of the mutable Cluster attributes may be set as keyword arguments to the constructor.
.. autoattribute:: cql_version
.. autoattribute:: protocol_version

View File

@@ -0,0 +1,81 @@
``cassandra.cqlengine.columns`` - Column types for object mapping models
========================================================================
.. module:: cassandra.cqlengine.columns
Columns
-------
Columns in your models map to columns in your CQL table. You define CQL columns by defining column attributes on your model classes.
For a model to be valid it needs at least one primary key column and one non-primary key column.
Just as in CQL, the order you define your columns in is important, and is the same order they are defined in on a model's corresponding table.
Each column on your model definitions needs to be an instance of a Column class.
.. autoclass:: Column(**kwargs)
.. autoattribute:: primary_key
.. autoattribute:: primary_key
.. autoattribute:: partition_key
.. autoattribute:: index
.. autoattribute:: db_field
.. autoattribute:: default
.. autoattribute:: required
.. autoattribute:: clustering_order
.. autoattribute:: polymorphic_key
.. autoattribute:: static
Column Types
------------
Columns of all types are initialized by passing :class:`.Column` attributes to the constructor by keyword.
.. autoclass:: Ascii(**kwargs)
.. autoclass:: BigInt(**kwargs)
.. autoclass:: Blob(**kwargs)
.. autoclass:: Bytes(**kwargs)
.. autoclass:: Boolean(**kwargs)
.. autoclass:: Date(**kwargs)
.. autoclass:: DateTime(**kwargs)
.. autoclass:: Decimal(**kwargs)
.. autoclass:: Float(**kwargs)
.. autoclass:: Integer(**kwargs)
.. autoclass:: List(**kwargs)
.. autoclass:: Map(**kwargs)
.. autoclass:: Set(**kwargs)
.. autoclass:: Text(**kwargs)
In addition to the :class:Column attributes, ``Text`` columns accept the following attributes for validation:
.. autoattribute:: min_length
.. autoattribute:: max_length
.. autoclass:: TimeUUID(**kwargs)
.. autoclass:: UUID(**kwargs)
.. autoclass:: VarInt(**kwargs)

View File

@@ -0,0 +1,215 @@
``cassandra.cqlengine.models`` - Table models for object mapping
================================================================
.. module:: cassandra.cqlengine.models
Model
-----
.. autoclass:: Model(\*\*kwargs)
The initializer creates an instance of the model. Pass in keyword arguments for columns you've defined on the model.
.. code-block:: python
class Person(Model):
id = columns.UUID(primary_key=True)
first_name = columns.Text()
last_name = columns.Text()
person = Person(first_name='Blake', last_name='Eggleston')
person.first_name #returns 'Blake'
person.last_name #returns 'Eggleston'
*Model attributes define how the model maps to tables in the database. These are class variables that should be set
when defining Model deriviatives.*
.. autoattribute:: __abstract__
:annotation: = False
.. autoattribute:: __table_name__
.. autoattribute:: __keyspace__
.. _ttl-change:
.. autoattribute:: __default_ttl__
.. autoattribute:: __polymorphic_key__
See :ref:`table_polymorphism` for usage examples.
*Each table can have its own set of configuration options.
These can be specified on a model with the following attributes:*
See the `list of supported table properties for more information
<http://www.datastax.com/documentation/cql/3.1/cql/cql_reference/tabProp.html>`_.
.. attribute:: __bloom_filter_fp_chance
.. attribute:: __caching__
.. attribute:: __comment__
.. attribute:: __dclocal_read_repair_chance__
.. attribute:: __default_time_to_live__
.. attribute:: __gc_grace_seconds__
.. attribute:: __index_interval__
.. attribute:: __memtable_flush_period_in_ms__
.. attribute:: __populate_io_cache_on_flush__
.. attribute:: __read_repair_chance__
.. attribute:: __replicate_on_write__
*Model presently supports specifying compaction options via class attributes.
cqlengine will only use your compaction options if you have a strategy set.
When a table is synced, it will be altered to match the compaction options set on your table.
This means that if you are changing settings manually they will be changed back on resync.*
*Do not use the compaction settings of cqlengine if you want to manage your compaction settings manually.*
*cqlengine supports all compaction options as of Cassandra 1.2.8.*
**These attibutes will likely be replaced by a single options string in a future version, and are therefore deprecated.**
.. attribute:: __compaction_bucket_high__
:annotation: Deprecated
.. attribute:: __compaction_bucket_low__
:annotation: Deprecated
.. attribute:: __compaction_max_compaction_threshold__
:annotation: Deprecated
.. attribute:: __compaction_min_compaction_threshold__
:annotation: Deprecated
.. attribute:: __compaction_min_sstable_size__
:annotation: Deprecated
.. attribute:: __compaction_sstable_size_in_mb__
:annotation: Deprecated
.. attribute:: __compaction_tombstone_compaction_interval__
:annotation: Deprecated
.. attribute:: __compaction_tombstone_threshold__
:annotation: Deprecated
For example:
.. code-block:: python
class User(Model):
__compaction__ = cqlengine.LeveledCompactionStrategy
__compaction_sstable_size_in_mb__ = 64
__compaction_tombstone_threshold__ = .2
user_id = columns.UUID(primary_key=True)
name = columns.Text()
or for SizeTieredCompaction:
.. code-block:: python
class TimeData(Model):
__compaction__ = SizeTieredCompactionStrategy
__compaction_bucket_low__ = .3
__compaction_bucket_high__ = 2
__compaction_min_threshold__ = 2
__compaction_max_threshold__ = 64
__compaction_tombstone_compaction_interval__ = 86400
Tables may use `LeveledCompactionStrategy` or `SizeTieredCompactionStrategy`. Both options are available in the top level cqlengine module. To reiterate, you will need to set your `__compaction__` option explicitly in order for cqlengine to handle any of your settings.
*The base methods allow creating, storing, and querying modeled objects.*
.. automethod:: create
.. method:: if_not_exists()
Check the existence of an object before insertion. The existence of an
object is determined by its primary key(s). And please note using this flag
would incur performance cost.
if the insertion didn't applied, a LWTException exception would be raised.
.. code-block:: python
try:
TestIfNotExistsModel.if_not_exists().create(id=id, count=9, text='111111111111')
except LWTException as e:
# handle failure case
print e.existing # existing object
This method is supported on Cassandra 2.0 or later.
.. automethod:: save
.. automethod:: update
.. method:: iff(**values)
Checks to ensure that the values specified are correct on the Cassandra cluster.
Simply specify the column(s) and the expected value(s). As with if_not_exists,
this incurs a performance cost.
If the insertion isn't applied, a LWTException is raised
.. code-block:: python
t = TestTransactionModel(text='some text', count=5)
try:
t.iff(count=5).update('other text')
except LWTException as e:
# handle failure
.. automethod:: get
.. automethod:: filter
.. automethod:: all
.. automethod:: delete
.. method:: batch(batch_object)
Sets the batch object to run instance updates and inserts queries with.
See :doc:`/cqlengine/batches` for usage examples
.. automethod:: timeout
.. method:: timestamp(timedelta_or_datetime)
Sets the timestamp for the query
.. method:: ttl(ttl_in_sec)
Sets the ttl values to run instance updates and inserts queries with.
.. automethod:: column_family_name
Models also support dict-like access:
.. method:: len(m)
Returns the number of columns defined in the model
.. method:: m[col_name]
Returns the value of column ``col_name``
.. method:: m[col_name] = value
Set ``m[col_name]`` to value
.. automethod:: keys
.. automethod:: values
.. automethod:: items

View File

@@ -0,0 +1,42 @@
``cassandra.cqlengine.query`` - Query and filter model objects
=================================================================
.. module:: cassandra.cqlengine.query
QuerySet
--------
QuerySet objects are typically obtained by calling :meth:`~.cassandra.cqlengine.models.Model.objects` on a model class.
The mehtods here are used to filter, order, and constrain results.
.. autoclass:: ModelQuerySet
.. automethod:: all
.. automethod:: batch
.. automethod:: consistency
.. automethod:: count
.. automethod:: filter
.. automethod:: get
.. automethod:: limit
.. automethod:: order_by
.. automethod:: allow_filtering
.. automethod:: timestamp
.. automethod:: ttl
.. _blind_updates:
.. automethod:: update
.. autoclass:: DoesNotExist
.. autoclass:: MultipleObjectsReturned

View File

@@ -1,6 +1,8 @@
API Documentation
=================
Core Driver
-----------
.. toctree::
:maxdepth: 2
@@ -22,3 +24,12 @@ API Documentation
cassandra/io/libevreactor
cassandra/io/geventreactor
cassandra/io/twistedreactor
Object Mapper
-------------
.. toctree::
:maxdepth: 1
cassandra/cqlengine/models
cassandra/cqlengine/columns
cassandra/cqlengine/query

View File

@@ -55,7 +55,7 @@ version = cassandra.__version__
release = cassandra.__version__
autodoc_member_order = 'bysource'
autoclass_content = 'both'
autoclass_content = 'class'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

108
docs/cqlengine/batches.rst Normal file
View File

@@ -0,0 +1,108 @@
=============
Batch Queries
=============
cqlengine supports batch queries using the BatchQuery class. Batch queries can be started and stopped manually, or within a context manager. To add queries to the batch object, you just need to precede the create/save/delete call with a call to batch, and pass in the batch object.
Batch Query General Use Pattern
===============================
You can only create, update, and delete rows with a batch query, attempting to read rows out of the database with a batch query will fail.
.. code-block:: python
from cqlengine import BatchQuery
#using a context manager
with BatchQuery() as b:
now = datetime.now()
em1 = ExampleModel.batch(b).create(example_type=0, description="1", created_at=now)
em2 = ExampleModel.batch(b).create(example_type=0, description="2", created_at=now)
em3 = ExampleModel.batch(b).create(example_type=0, description="3", created_at=now)
# -- or --
#manually
b = BatchQuery()
now = datetime.now()
em1 = ExampleModel.batch(b).create(example_type=0, description="1", created_at=now)
em2 = ExampleModel.batch(b).create(example_type=0, description="2", created_at=now)
em3 = ExampleModel.batch(b).create(example_type=0, description="3", created_at=now)
b.execute()
# updating in a batch
b = BatchQuery()
em1.description = "new description"
em1.batch(b).save()
em2.description = "another new description"
em2.batch(b).save()
b.execute()
# deleting in a batch
b = BatchQuery()
ExampleModel.objects(id=some_id).batch(b).delete()
ExampleModel.objects(id=some_id2).batch(b).delete()
b.execute()
Typically you will not want the block to execute if an exception occurs inside the `with` block. However, in the case that this is desirable, it's achievable by using the following syntax:
.. code-block:: python
with BatchQuery(execute_on_exception=True) as b:
LogEntry.batch(b).create(k=1, v=1)
mystery_function() # exception thrown in here
LogEntry.batch(b).create(k=1, v=2) # this code is never reached due to the exception, but anything leading up to here will execute in the batch.
If an exception is thrown somewhere in the block, any statements that have been added to the batch will still be executed. This is useful for some logging situations.
Batch Query Execution Callbacks
===============================
In order to allow secondary tasks to be chained to the end of batch, BatchQuery instances allow callbacks to be
registered with the batch, to be executed immediately after the batch executes.
Multiple callbacks can be attached to same BatchQuery instance, they are executed in the same order that they
are added to the batch.
The callbacks attached to a given batch instance are executed only if the batch executes. If the batch is used as a
context manager and an exception is raised, the queued up callbacks will not be run.
.. code-block:: python
def my_callback(*args, **kwargs):
pass
batch = BatchQuery()
batch.add_callback(my_callback)
batch.add_callback(my_callback, 'positional arg', named_arg='named arg value')
# if you need reference to the batch within the callback,
# just trap it in the arguments to be passed to the callback:
batch.add_callback(my_callback, cqlengine_batch=batch)
# once the batch executes...
batch.execute()
# the effect of the above scheduled callbacks will be similar to
my_callback()
my_callback('positional arg', named_arg='named arg value')
my_callback(cqlengine_batch=batch)
Failure in any of the callbacks does not affect the batch's execution, as the callbacks are started after the execution
of the batch is complete.
Logged vs Unlogged Batches
---------------------------
By default, queries in cqlengine are LOGGED, which carries additional overhead from UNLOGGED. To explicitly state which batch type to use, simply:
.. code-block:: python
from cqlengine.query import BatchType
with BatchQuery(batch_type=BatchType.Unlogged) as b:
LogEntry.batch(b).create(k=1, v=1)
LogEntry.batch(b).create(k=1, v=2)

View File

@@ -2,10 +2,6 @@
Columns
=======
**Users of versions < 0.4, please read this post before upgrading:** `Breaking Changes`_
.. _Breaking Changes: https://groups.google.com/forum/?fromgroups#!topic/cqlengine-users/erkSNe1JwuU
.. module:: cqlengine.columns
.. class:: Bytes()

View File

@@ -2,10 +2,6 @@
Connection
==========
**Users of versions < 0.4, please read this post before upgrading:** `Breaking Changes`_
.. _Breaking Changes: https://groups.google.com/forum/?fromgroups#!topic/cqlengine-users/erkSNe1JwuU
.. module:: cqlengine.connection
The setup function in `cqlengine.connection` records the Cassandra servers to connect to.

View File

@@ -2,25 +2,26 @@
Models
======
**Users of versions < 0.4, please read this post before upgrading:** `Breaking Changes`_
.. _Breaking Changes: https://groups.google.com/forum/?fromgroups#!topic/cqlengine-users/erkSNe1JwuU
.. module:: cqlengine.connection
.. module:: cqlengine.models
A model is a python class representing a CQL table.
A model is a python class representing a CQL table. Models derive from :class:`Model`, and
define basic table properties and columns for a table.
Examples
========
Columns in your models map to columns in your CQL table. You define CQL columns by defining column attributes on your model classes.
For a model to be valid it needs at least one primary key column and one non-primary key column. Just as in CQL, the order you define
your columns in is important, and is the same order they are defined in on a model's corresponding table.
This example defines a Person table, with the columns ``first_name`` and ``last_name``
Some basic examples defining models are shown below. Consult the :doc:`Model API docs </api/cassandra/cqlengine/models>` and :doc:`Column API docs </api/cassandra/cqlengine/columns>` for complete details.
Example Definitions
===================
This example defines a ``Person`` table, with the columns ``first_name`` and ``last_name``
.. code-block:: python
from cqlengine import columns
from cqlengine.models import Model
from cassandra.cqlengine import columns
from cassandra.cqlengine.models import Model
class Person(Model):
id = columns.UUID(primary_key=True)
@@ -37,14 +38,14 @@ The Person model would create this CQL table:
first_name text,
last_name text,
PRIMARY KEY (id)
)
);
Here's an example of a comment table created with clustering keys, in descending order:
.. code-block:: python
from cqlengine import columns
from cqlengine.models import Model
from cassandra.cqlengine import columns
from cassandra.cqlengine.models import Model
class Comment(Model):
photo_id = columns.UUID(primary_key=True)
@@ -60,418 +61,122 @@ The Comment model's ``create table`` would look like the following:
comment_id timeuuid,
comment text,
PRIMARY KEY (photo_id, comment_id)
) WITH CLUSTERING ORDER BY (comment_id DESC)
) WITH CLUSTERING ORDER BY (comment_id DESC);
To sync the models to the database, you may do the following:
To sync the models to the database, you may do the following*:
.. code-block:: python
from cqlengine.management import sync_table
from cassandra.cqlengine.management import sync_table
sync_table(Person)
sync_table(Comment)
\*Note: synchronizing models causes schema changes, and should be done with caution.
Please see the discussion in :doc:`manage_schemas` for considerations.
Columns
=======
Columns in your models map to columns in your CQL table. You define CQL columns by defining column attributes on your model classes. For a model to be valid it needs at least one primary key column and one non-primary key column.
Just as in CQL, the order you define your columns in is important, and is the same order they are defined in on a model's corresponding table.
Column Types
============
Each column on your model definitions needs to an instance of a Column class. The column types that are included with cqlengine as of this writing are:
* :class:`~cqlengine.columns.Bytes`
* :class:`~cqlengine.columns.Ascii`
* :class:`~cqlengine.columns.Text`
* :class:`~cqlengine.columns.Integer`
* :class:`~cqlengine.columns.BigInt`
* :class:`~cqlengine.columns.DateTime`
* :class:`~cqlengine.columns.UUID`
* :class:`~cqlengine.columns.TimeUUID`
* :class:`~cqlengine.columns.Boolean`
* :class:`~cqlengine.columns.Float`
* :class:`~cqlengine.columns.Decimal`
* :class:`~cqlengine.columns.Set`
* :class:`~cqlengine.columns.List`
* :class:`~cqlengine.columns.Map`
Column Options
--------------
Each column can be defined with optional arguments to modify the way they behave. While some column types may
define additional column options, these are the options that are available on all columns:
:attr:`~cqlengine.columns.BaseColumn.primary_key`
If True, this column is created as a primary key field. A model can have multiple primary keys. Defaults to False.
*In CQL, there are 2 types of primary keys: partition keys and clustering keys. As with CQL, the first
primary key is the partition key, and all others are clustering keys, unless partition keys are specified
manually using* :attr:`~cqlengine.columns.BaseColumn.partition_key`
:attr:`~cqlengine.columns.BaseColumn.partition_key`
If True, this column is created as partition primary key. There may be many partition keys defined,
forming a *composite partition key*
:attr:`~cqlengine.columns.BaseColumn.clustering_order`
``ASC`` or ``DESC``, determines the clustering order of a clustering key.
:attr:`~cqlengine.columns.BaseColumn.index`
If True, an index will be created for this column. Defaults to False.
:attr:`~cqlengine.columns.BaseColumn.db_field`
Explicitly sets the name of the column in the database table. If this is left blank, the column name will be
the same as the name of the column attribute. Defaults to None.
:attr:`~cqlengine.columns.BaseColumn.default`
The default value for this column. If a model instance is saved without a value for this column having been
defined, the default value will be used. This can be either a value or a callable object (ie: datetime.now is a valid default argument).
Callable defaults will be called each time a default is assigned to a None value
:attr:`~cqlengine.columns.BaseColumn.required`
If True, this model cannot be saved without a value defined for this column. Defaults to False. Primary key fields always require values.
:attr:`~cqlengine.columns.BaseColumn.static`
Defined a column as static. Static columns are shared by all rows in a partition.
Model Methods
=============
Below are the methods that can be called on model instances.
.. class:: Model(\*\*values)
Creates an instance of the model. Pass in keyword arguments for columns you've defined on the model.
*Example*
.. code-block:: python
#using the person model from earlier:
class Person(Model):
id = columns.UUID(primary_key=True)
first_name = columns.Text()
last_name = columns.Text()
person = Person(first_name='Blake', last_name='Eggleston')
person.first_name #returns 'Blake'
person.last_name #returns 'Eggleston'
.. method:: save()
Saves an object to the database
*Example*
.. code-block:: python
#create a person instance
person = Person(first_name='Kimberly', last_name='Eggleston')
#saves it to Cassandra
person.save()
.. method:: delete()
Deletes the object from the database.
.. method:: batch(batch_object)
Sets the batch object to run instance updates and inserts queries with.
.. method:: timestamp(timedelta_or_datetime)
Sets the timestamp for the query
.. method:: ttl(ttl_in_sec)
Sets the ttl values to run instance updates and inserts queries with.
.. method:: if_not_exists()
Check the existence of an object before insertion. The existence of an
object is determined by its primary key(s). And please note using this flag
would incur performance cost.
if the insertion didn't applied, a LWTException exception would be raised.
*Example*
.. code-block:: python
try:
TestIfNotExistsModel.if_not_exists().create(id=id, count=9, text='111111111111')
except LWTException as e:
# handle failure case
print e.existing # existing object
This method is supported on Cassandra 2.0 or later.
.. method:: iff(**values)
Checks to ensure that the values specified are correct on the Cassandra cluster.
Simply specify the column(s) and the expected value(s). As with if_not_exists,
this incurs a performance cost.
If the insertion isn't applied, a LWTException is raised
.. code-block::
t = TestTransactionModel(text='some text', count=5)
try:
t.iff(count=5).update('other text')
except LWTException as e:
# handle failure
.. method:: update(**values)
Performs an update on the model instance. You can pass in values to set on the model
for updating, or you can call without values to execute an update against any modified
fields. If no fields on the model have been modified since loading, no query will be
performed. Model validation is performed normally.
It is possible to do a blind update, that is, to update a field without having first selected the object out of the database. See :ref:`Blind Updates <blind_updates>`
.. method:: get_changed_columns()
Returns a list of column names that have changed since the model was instantiated or saved
Model Attributes
================
.. attribute:: Model.__abstract__
*Optional.* Indicates that this model is only intended to be used as a base class for other models. You can't create tables for abstract models, but checks around schema validity are skipped during class construction.
.. attribute:: Model.__table_name__
*Optional.* Sets the name of the CQL table for this model. If left blank, the table name will be the name of the model, with it's module name as it's prefix. Manually defined table names are not inherited.
.. _keyspace-change:
.. attribute:: Model.__keyspace__
Sets the name of the keyspace used by this model.
**Prior to cqlengine 0.16, this setting defaulted
to 'cqlengine'. As of 0.16, this field needs to be set on all non-abstract models, or their base classes.**
.. _ttl-change:
.. attribute:: Model.__default_ttl__
Sets the default ttl used by this model. This can be overridden by using the ``ttl(ttl_in_sec)`` method.
Table Polymorphism
==================
As of cqlengine 0.8, it is possible to save and load different model classes using a single CQL table.
This is useful in situations where you have different object types that you want to store in a single cassandra row.
For instance, suppose you want a table that stores rows of pets owned by an owner:
.. code-block:: python
class Pet(Model):
__table_name__ = 'pet'
owner_id = UUID(primary_key=True)
pet_id = UUID(primary_key=True)
pet_type = Text(polymorphic_key=True)
name = Text()
def eat(self, food):
pass
def sleep(self, time):
pass
class Cat(Pet):
__polymorphic_key__ = 'cat'
cuteness = Float()
def tear_up_couch(self):
pass
class Dog(Pet):
__polymorphic_key__ = 'dog'
fierceness = Float()
def bark_all_night(self):
pass
After calling ``sync_table`` on each of these tables, the columns defined in each model will be added to the
``pet`` table. Additionally, saving ``Cat`` and ``Dog`` models will save the meta data needed to identify each row
as either a cat or dog.
To setup a polymorphic model structure, follow these steps
1. Create a base model with a column set as the polymorphic_key (set ``polymorphic_key=True`` in the column definition)
2. Create subclass models, and define a unique ``__polymorphic_key__`` value on each
3. Run ``sync_table`` on each of the sub tables
**About the polymorphic key**
The polymorphic key is what cqlengine uses under the covers to map logical cql rows to the appropriate model type. The
base model maintains a map of polymorphic keys to subclasses. When a polymorphic model is saved, this value is automatically
saved into the polymorphic key column. You can set the polymorphic key column to any column type that you like, with
the exception of container and counter columns, although ``Integer`` columns make the most sense. Additionally, if you
set ``index=True`` on your polymorphic key column, you can execute queries against polymorphic subclasses, and a
``WHERE`` clause will be automatically added to your query, returning only rows of that type. Note that you must
define a unique ``__polymorphic_key__`` value to each subclass, and that you can only assign a single polymorphic
key column per model
Extending Model Validation
==========================
Each time you save a model instance in cqlengine, the data in the model is validated against the schema you've defined
for your model. Most of the validation is fairly straightforward, it basically checks that you're not trying to do
something like save text into an integer column, and it enforces the ``required`` flag set on column definitions.
It also performs any transformations needed to save the data properly.
However, there are often additional constraints or transformations you want to impose on your data, beyond simply
making sure that Cassandra won't complain when you try to insert it. To define additional validation on a model,
extend the model's validation method:
.. code-block:: python
class Member(Model):
person_id = UUID(primary_key=True)
name = Text(required=True)
def validate(self):
super(Member, self).validate()
if self.name == 'jon':
raise ValidationError('no jon\'s allowed')
*Note*: while not required, the convention is to raise a ``ValidationError`` (``from cqlengine import ValidationError``)
if validation fails
Table Properties
================
Each table can have its own set of configuration options.
These can be specified on a model with the following attributes:
.. attribute:: Model.__bloom_filter_fp_chance
.. attribute:: Model.__caching__
.. attribute:: Model.__comment__
.. attribute:: Model.__dclocal_read_repair_chance__
.. attribute:: Model.__default_time_to_live__
.. attribute:: Model.__gc_grace_seconds__
.. attribute:: Model.__index_interval__
.. attribute:: Model.__memtable_flush_period_in_ms__
.. attribute:: Model.__populate_io_cache_on_flush__
.. attribute:: Model.__read_repair_chance__
.. attribute:: Model.__replicate_on_write__
Example:
.. code-block:: python
from cqlengine import CACHING_ROWS_ONLY, columns
from cqlengine.models import Model
class User(Model):
__caching__ = CACHING_ROWS_ONLY # cache only rows instead of keys only by default
__gc_grace_seconds__ = 86400 # 1 day instead of the default 10 days
user_id = columns.UUID(primary_key=True)
name = columns.Text()
Will produce the following CQL statement:
.. code-block:: sql
CREATE TABLE cqlengine.user (
user_id uuid,
name text,
PRIMARY KEY (user_id)
) WITH caching = 'rows_only'
AND gc_grace_seconds = 86400;
See the `list of supported table properties for more information
<http://www.datastax.com/documentation/cql/3.1/cql/cql_reference/tabProp.html>`_.
Compaction Options
==================
As of cqlengine 0.7 we've added support for specifying compaction options. cqlengine will only use your compaction options if you have a strategy set. When a table is synced, it will be altered to match the compaction options set on your table. This means that if you are changing settings manually they will be changed back on resync. Do not use the compaction settings of cqlengine if you want to manage your compaction settings manually.
cqlengine supports all compaction options as of Cassandra 1.2.8.
Available Options:
.. attribute:: Model.__compaction_bucket_high__
.. attribute:: Model.__compaction_bucket_low__
.. attribute:: Model.__compaction_max_compaction_threshold__
.. attribute:: Model.__compaction_min_compaction_threshold__
.. attribute:: Model.__compaction_min_sstable_size__
.. attribute:: Model.__compaction_sstable_size_in_mb__
.. attribute:: Model.__compaction_tombstone_compaction_interval__
.. attribute:: Model.__compaction_tombstone_threshold__
For example:
.. code-block:: python
class User(Model):
__compaction__ = cqlengine.LeveledCompactionStrategy
__compaction_sstable_size_in_mb__ = 64
__compaction_tombstone_threshold__ = .2
user_id = columns.UUID(primary_key=True)
name = columns.Text()
or for SizeTieredCompaction:
.. code-block:: python
class TimeData(Model):
__compaction__ = SizeTieredCompactionStrategy
__compaction_bucket_low__ = .3
__compaction_bucket_high__ = 2
__compaction_min_threshold__ = 2
__compaction_max_threshold__ = 64
__compaction_tombstone_compaction_interval__ = 86400
Tables may use `LeveledCompactionStrategy` or `SizeTieredCompactionStrategy`. Both options are available in the top level cqlengine module. To reiterate, you will need to set your `__compaction__` option explicitly in order for cqlengine to handle any of your settings.
For examples on manipulating data and creating queries, see :doc:`queryset`
Manipulating model instances as dictionaries
============================================
As of cqlengine 0.12, we've added support for treating model instances like dictionaries. See below for examples.
Model instances can be accessed like dictionaries.
.. code-block:: python
.. code-block:: python
class Person(Model):
first_name = columns.Text()
last_name = columns.Text()
class Person(Model):
first_name = columns.Text()
last_name = columns.Text()
kevin = Person.create(first_name="Kevin", last_name="Deldycke")
dict(kevin) # returns {'first_name': 'Kevin', 'last_name': 'Deldycke'}
kevin['first_name'] # returns 'Kevin'
kevin.keys() # returns ['first_name', 'last_name']
kevin.values() # returns ['Kevin', 'Deldycke']
kevin.items() # returns [('first_name', 'Kevin'), ('last_name', 'Deldycke')]
kevin = Person.create(first_name="Kevin", last_name="Deldycke")
dict(kevin) # returns {'first_name': 'Kevin', 'last_name': 'Deldycke'}
kevin['first_name'] # returns 'Kevin'
kevin.keys() # returns ['first_name', 'last_name']
kevin.values() # returns ['Kevin', 'Deldycke']
kevin.items() # returns [('first_name', 'Kevin'), ('last_name', 'Deldycke')]
kevin['first_name'] = 'KEVIN5000' # changes the models first name
kevin['first_name'] = 'KEVIN5000' # changes the models first name
Extending Model Validation
==========================
Each time you save a model instance in cqlengine, the data in the model is validated against the schema you've defined
for your model. Most of the validation is fairly straightforward, it basically checks that you're not trying to do
something like save text into an integer column, and it enforces the ``required`` flag set on column definitions.
It also performs any transformations needed to save the data properly.
However, there are often additional constraints or transformations you want to impose on your data, beyond simply
making sure that Cassandra won't complain when you try to insert it. To define additional validation on a model,
extend the model's validation method:
.. code-block:: python
class Member(Model):
person_id = UUID(primary_key=True)
name = Text(required=True)
def validate(self):
super(Member, self).validate()
if self.name == 'jon':
raise ValidationError('no jon\'s allowed')
*Note*: while not required, the convention is to raise a ``ValidationError`` (``from cqlengine import ValidationError``)
if validation fails.
.. _table_polymorphism:
Table Polymorphism
==================
It is possible to save and load different model classes using a single CQL table.
This is useful in situations where you have different object types that you want to store in a single cassandra row.
For instance, suppose you want a table that stores rows of pets owned by an owner:
.. code-block:: python
class Pet(Model):
__table_name__ = 'pet'
owner_id = UUID(primary_key=True)
pet_id = UUID(primary_key=True)
pet_type = Text(polymorphic_key=True)
name = Text()
def eat(self, food):
pass
def sleep(self, time):
pass
class Cat(Pet):
__polymorphic_key__ = 'cat'
cuteness = Float()
def tear_up_couch(self):
pass
class Dog(Pet):
__polymorphic_key__ = 'dog'
fierceness = Float()
def bark_all_night(self):
pass
After calling ``sync_table`` on each of these tables, the columns defined in each model will be added to the
``pet`` table. Additionally, saving ``Cat`` and ``Dog`` models will save the meta data needed to identify each row
as either a cat or dog.
To setup a polymorphic model structure, follow these steps
1. Create a base model with a column set as the polymorphic_key (set ``polymorphic_key=True`` in the column definition)
2. Create subclass models, and define a unique ``__polymorphic_key__`` value on each
3. Run ``sync_table`` on each of the sub tables
**About the polymorphic key**
The polymorphic key is what cqlengine uses under the covers to map logical cql rows to the appropriate model type. The
base model maintains a map of polymorphic keys to subclasses. When a polymorphic model is saved, this value is automatically
saved into the polymorphic key column. You can set the polymorphic key column to any column type that you like, with
the exception of container and counter columns, although ``Integer`` columns make the most sense. Additionally, if you
set ``index=True`` on your polymorphic key column, you can execute queries against polymorphic subclasses, and a
``WHERE`` clause will be automatically added to your query, returning only rows of that type. Note that you must
define a unique ``__polymorphic_key__`` value to each subclass, and that you can only assign a single polymorphic
key column per model

View File

@@ -2,13 +2,7 @@
Making Queries
==============
**Users of versions < 0.4, please read this post before upgrading:** `Breaking Changes`_
.. _Breaking Changes: https://groups.google.com/forum/?fromgroups#!topic/cqlengine-users/erkSNe1JwuU
.. module:: cqlengine.connection
.. module:: cqlengine.query
.. module:: cqlengine.queryset
Retrieving objects
==================
@@ -278,320 +272,6 @@ Values Lists
values = list(TestModel.objects.values_list('clustering_key', flat=True))
# [19L, 18L, 17L, 16L, 15L, 14L, 13L, 12L, 11L, 10L, 9L, 8L, 7L, 6L, 5L, 4L, 3L, 2L, 1L, 0L]
Batch Queries
=============
cqlengine now supports batch queries using the BatchQuery class. Batch queries can be started and stopped manually, or within a context manager. To add queries to the batch object, you just need to precede the create/save/delete call with a call to batch, and pass in the batch object.
Batch Query General Use Pattern
-------------------------------
You can only create, update, and delete rows with a batch query, attempting to read rows out of the database with a batch query will fail.
.. code-block:: python
from cqlengine import BatchQuery
#using a context manager
with BatchQuery() as b:
now = datetime.now()
em1 = ExampleModel.batch(b).create(example_type=0, description="1", created_at=now)
em2 = ExampleModel.batch(b).create(example_type=0, description="2", created_at=now)
em3 = ExampleModel.batch(b).create(example_type=0, description="3", created_at=now)
# -- or --
#manually
b = BatchQuery()
now = datetime.now()
em1 = ExampleModel.batch(b).create(example_type=0, description="1", created_at=now)
em2 = ExampleModel.batch(b).create(example_type=0, description="2", created_at=now)
em3 = ExampleModel.batch(b).create(example_type=0, description="3", created_at=now)
b.execute()
# updating in a batch
b = BatchQuery()
em1.description = "new description"
em1.batch(b).save()
em2.description = "another new description"
em2.batch(b).save()
b.execute()
# deleting in a batch
b = BatchQuery()
ExampleModel.objects(id=some_id).batch(b).delete()
ExampleModel.objects(id=some_id2).batch(b).delete()
b.execute()
Typically you will not want the block to execute if an exception occurs inside the `with` block. However, in the case that this is desirable, it's achievable by using the following syntax:
.. code-block:: python
with BatchQuery(execute_on_exception=True) as b:
LogEntry.batch(b).create(k=1, v=1)
mystery_function() # exception thrown in here
LogEntry.batch(b).create(k=1, v=2) # this code is never reached due to the exception, but anything leading up to here will execute in the batch.
If an exception is thrown somewhere in the block, any statements that have been added to the batch will still be executed. This is useful for some logging situations.
Batch Query Execution Callbacks
-------------------------------
In order to allow secondary tasks to be chained to the end of batch, BatchQuery instances allow callbacks to be
registered with the batch, to be executed immediately after the batch executes.
Multiple callbacks can be attached to same BatchQuery instance, they are executed in the same order that they
are added to the batch.
The callbacks attached to a given batch instance are executed only if the batch executes. If the batch is used as a
context manager and an exception is raised, the queued up callbacks will not be run.
.. code-block:: python
def my_callback(*args, **kwargs):
pass
batch = BatchQuery()
batch.add_callback(my_callback)
batch.add_callback(my_callback, 'positional arg', named_arg='named arg value')
# if you need reference to the batch within the callback,
# just trap it in the arguments to be passed to the callback:
batch.add_callback(my_callback, cqlengine_batch=batch)
# once the batch executes...
batch.execute()
# the effect of the above scheduled callbacks will be similar to
my_callback()
my_callback('positional arg', named_arg='named arg value')
my_callback(cqlengine_batch=batch)
Failure in any of the callbacks does not affect the batch's execution, as the callbacks are started after the execution
of the batch is complete.
Logged vs Unlogged Batches
---------------------------
By default, queries in cqlengine are LOGGED, which carries additional overhead from UNLOGGED. To explicitly state which batch type to use, simply:
.. code-block:: python
from cqlengine.query import BatchType
with BatchQuery(batch_type=BatchType.Unlogged) as b:
LogEntry.batch(b).create(k=1, v=1)
LogEntry.batch(b).create(k=1, v=2)
QuerySet method reference
=========================
.. class:: QuerySet
.. method:: all()
Returns a queryset matching all rows
.. code-block:: python
for user in User.objects().all():
print(user)
.. method:: batch(batch_object)
Sets the batch object to run the query on. Note that running a select query with a batch object will raise an exception
.. method:: consistency(consistency_setting)
Sets the consistency level for the operation. Options may be imported from the top level :attr:`cqlengine` package.
.. code-block:: python
for user in User.objects(id=3).consistency(ONE):
print(user)
.. method:: count()
Returns the number of matching rows in your QuerySet
.. code-block:: python
print(User.objects().count())
.. method:: filter(\*\*values)
:param values: See :ref:`retrieving-objects-with-filters`
Returns a QuerySet filtered on the keyword arguments
.. method:: get(\*\*values)
:param values: See :ref:`retrieving-objects-with-filters`
Returns a single object matching the QuerySet. If no objects are matched, a :attr:`~models.Model.DoesNotExist` exception is raised. If more than one object is found, a :attr:`~models.Model.MultipleObjectsReturned` exception is raised.
.. code-block:: python
user = User.get(id=1)
.. method:: limit(num)
Limits the number of results returned by Cassandra.
*Note that CQL's default limit is 10,000, so all queries without a limit set explicitly will have an implicit limit of 10,000*
.. code-block:: python
for user in User.objects().limit(100):
print(user)
.. method:: order_by(field_name)
:param field_name: the name of the field to order on. *Note: the field_name must be a clustering key*
:type field_name: string
Sets the field to order on.
.. code-block:: python
from uuid import uuid1,uuid4
class Comment(Model):
photo_id = UUID(primary_key=True)
comment_id = TimeUUID(primary_key=True, default=uuid1) # auto becomes clustering key
comment = Text()
sync_table(Comment)
u = uuid4()
for x in range(5):
Comment.create(photo_id=u, comment="test %d" % x)
print("Normal")
for comment in Comment.objects(photo_id=u):
print comment.comment_id
print("Reversed")
for comment in Comment.objects(photo_id=u).order_by("-comment_id"):
print comment.comment_id
.. method:: allow_filtering()
Enables the (usually) unwise practive of querying on a clustering key without also defining a partition key
.. method:: timestamp(timestamp_or_long_or_datetime)
Allows for custom timestamps to be saved with the record.
.. method:: ttl(ttl_in_seconds)
:param ttl_in_seconds: time in seconds in which the saved values should expire
:type ttl_in_seconds: int
Sets the ttl to run the query query with. Note that running a select query with a ttl value will raise an exception
.. _blind_updates:
.. method:: update(**values)
Performs an update on the row selected by the queryset. Include values to update in the
update like so:
.. code-block:: python
Model.objects(key=n).update(value='x')
Passing in updates for columns which are not part of the model will raise a ValidationError.
Per column validation will be performed, but instance level validation will not
(`Model.validate` is not called). This is sometimes referred to as a blind update.
For example:
.. code-block:: python
class User(Model):
id = Integer(primary_key=True)
name = Text()
setup(["localhost"], "test")
sync_table(User)
u = User.create(id=1, name="jon")
User.objects(id=1).update(name="Steve")
# sets name to null
User.objects(id=1).update(name=None)
The queryset update method also supports blindly adding and removing elements from container columns, without
loading a model instance from Cassandra.
Using the syntax `.update(column_name={x, y, z})` will overwrite the contents of the container, like updating a
non container column. However, adding `__<operation>` to the end of the keyword arg, makes the update call add
or remove items from the collection, without overwriting then entire column.
Given the model below, here are the operations that can be performed on the different container columns:
.. code-block:: python
class Row(Model):
row_id = columns.Integer(primary_key=True)
set_column = columns.Set(Integer)
list_column = columns.Set(Integer)
map_column = columns.Set(Integer, Integer)
:class:`~cqlengine.columns.Set`
- `add`: adds the elements of the given set to the column
- `remove`: removes the elements of the given set to the column
.. code-block:: python
# add elements to a set
Row.objects(row_id=5).update(set_column__add={6})
# remove elements to a set
Row.objects(row_id=5).update(set_column__remove={4})
:class:`~cqlengine.columns.List`
- `append`: appends the elements of the given list to the end of the column
- `prepend`: prepends the elements of the given list to the beginning of the column
.. code-block:: python
# append items to a list
Row.objects(row_id=5).update(list_column__append=[6, 7])
# prepend items to a list
Row.objects(row_id=5).update(list_column__prepend=[1, 2])
:class:`~cqlengine.columns.Map`
- `update`: adds the given keys/values to the columns, creating new entries if they didn't exist, and overwriting old ones if they did
.. code-block:: python
# add items to a map
Row.objects(row_id=5).update(map_column__update={1: 2, 3: 4})
Per Query Timeouts
===================

View File

@@ -18,6 +18,9 @@ Contents
:doc:`getting_started`
A guide through the first steps of connecting to Cassandra and executing queries.
:doc:`object_mapper`
Introduction to the integrated object mapper, cqlengine
:doc:`api/index`
The API documentation.
@@ -36,6 +39,19 @@ Contents
:doc:`security`
An overview of the security features of the driver.
.. toctree::
:hidden:
api/index
installation
getting_started
upgrading
performance
query_paging
security
user_defined_types
object_mapper
Getting Help
------------
Please send questions to the `mailing list <https://groups.google.com/a/lists.datastax.com/forum/#!forum/python-driver-user>`_.
@@ -50,135 +66,9 @@ Please report any bugs and make any feature requests on the
If you would like to contribute, please feel free to open a pull request.
.. toctree::
:hidden:
api/index
installation
getting_started
upgrading
performance
query_paging
security
user_defined_types
Indices and Tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
..
cqlengine documentation
=======================
**Users of versions < 0.16, the default keyspace 'cqlengine' has been removed. Please read this before upgrading:** :ref:`Breaking Changes <keyspace-change>`
cqlengine is a Cassandra CQL 3 Object Mapper for Python
:ref:`getting-started`
Download
========
`Github <https://github.com/cqlengine/cqlengine>`_
`PyPi <https://pypi.python.org/pypi/cqlengine>`_
Contents:
=========
.. toctree::
:maxdepth: 2
topics/models
topics/queryset
topics/columns
topics/connection
topics/manage_schemas
topics/external_resources
topics/related_projects
topics/third_party
topics/development
topics/faq
.. _getting-started:
Getting Started
===============
.. code-block:: python
#first, define a model
from cqlengine import columns
from cqlengine import Model
class ExampleModel(Model):
example_id = columns.UUID(primary_key=True, default=uuid.uuid4)
example_type = columns.Integer(index=True)
created_at = columns.DateTime()
description = columns.Text(required=False)
#next, setup the connection to your cassandra server(s)...
>>> from cqlengine import connection
# see http://datastax.github.io/python-driver/api/cassandra/cluster.html for options
# the list of hosts will be passed to create a Cluster() instance
>>> connection.setup(['127.0.0.1'], "cqlengine")
# if you're connecting to a 1.2 cluster
>>> connection.setup(['127.0.0.1'], "cqlengine", protocol_version=1)
#...and create your CQL table
>>> from cqlengine.management import sync_table
>>> sync_table(ExampleModel)
#now we can create some rows:
>>> em1 = ExampleModel.create(example_type=0, description="example1", created_at=datetime.now())
>>> em2 = ExampleModel.create(example_type=0, description="example2", created_at=datetime.now())
>>> em3 = ExampleModel.create(example_type=0, description="example3", created_at=datetime.now())
>>> em4 = ExampleModel.create(example_type=0, description="example4", created_at=datetime.now())
>>> em5 = ExampleModel.create(example_type=1, description="example5", created_at=datetime.now())
>>> em6 = ExampleModel.create(example_type=1, description="example6", created_at=datetime.now())
>>> em7 = ExampleModel.create(example_type=1, description="example7", created_at=datetime.now())
>>> em8 = ExampleModel.create(example_type=1, description="example8", created_at=datetime.now())
#and now we can run some queries against our table
>>> ExampleModel.objects.count()
8
>>> q = ExampleModel.objects(example_type=1)
>>> q.count()
4
>>> for instance in q:
>>> print instance.description
example5
example6
example7
example8
#here we are applying additional filtering to an existing query
#query objects are immutable, so calling filter returns a new
#query object
>>> q2 = q.filter(example_id=em5.example_id)
>>> q2.count()
1
>>> for instance in q2:
>>> print instance.description
example5
`Report a Bug <https://github.com/cqlengine/cqlengine/issues>`_
`Users Mailing List <https://groups.google.com/forum/?fromgroups#!forum/cqlengine-users>`_
Indices and tables
>>>>>>> cqlengine/master
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

102
docs/object_mapper.rst Normal file
View File

@@ -0,0 +1,102 @@
Object Mapper
=============
cqlengine is the Cassandra CQL 3 Object Mapper packaged with this driver
:ref:`getting-started`
Contents
--------
:doc:`cqlengine/models`
Mapping objects to tables
:doc:`cqlengine/queryset`
Query sets, filtering
:doc:`cqlengine/batches`
Batch mutations
:doc:`cqlengine/connection`
Managing connections
:doc:`cqlengine/manage_schemas`
:doc:`cqlengine/external_resources`
:doc:`cqlengine/related_projects`
:doc:`cqlengine/third_party`
:doc:`cqlengine/development`
:doc:`cqlengine/faq`
.. toctree::
:hidden:
cqlengine/models
cqlengine/queryset
cqlengine/batches
.. _getting-started:
Getting Started
---------------
.. code-block:: python
import uuid
from cassandra.cqlengine import columns
from cassandra.cqlengine import connection
from datetime import datetime
from cassandra.cqlengine.management import create_keyspace, sync_table
from cassandra.cqlengine.models import Model
#first, define a model
class ExampleModel(Model):
example_id = columns.UUID(primary_key=True, default=uuid.uuid4)
example_type = columns.Integer(index=True)
created_at = columns.DateTime()
description = columns.Text(required=False)
#next, setup the connection to your cassandra server(s)...
# see http://datastax.github.io/python-driver/api/cassandra/cluster.html for options
# the list of hosts will be passed to create a Cluster() instance
connection.setup(['127.0.0.1'], "cqlengine", protocol_version=3)
#...and create your CQL table
>>> sync_table(ExampleModel)
#now we can create some rows:
>>> em1 = ExampleModel.create(example_type=0, description="example1", created_at=datetime.now())
>>> em2 = ExampleModel.create(example_type=0, description="example2", created_at=datetime.now())
>>> em3 = ExampleModel.create(example_type=0, description="example3", created_at=datetime.now())
>>> em4 = ExampleModel.create(example_type=0, description="example4", created_at=datetime.now())
>>> em5 = ExampleModel.create(example_type=1, description="example5", created_at=datetime.now())
>>> em6 = ExampleModel.create(example_type=1, description="example6", created_at=datetime.now())
>>> em7 = ExampleModel.create(example_type=1, description="example7", created_at=datetime.now())
>>> em8 = ExampleModel.create(example_type=1, description="example8", created_at=datetime.now())
#and now we can run some queries against our table
>>> ExampleModel.objects.count()
8
>>> q = ExampleModel.objects(example_type=1)
>>> q.count()
4
>>> for instance in q:
>>> print instance.description
example5
example6
example7
example8
#here we are applying additional filtering to an existing query
#query objects are immutable, so calling filter returns a new
#query object
>>> q2 = q.filter(example_id=em5.example_id)
>>> q2.count()
1
>>> for instance in q2:
>>> print instance.description
example5