cqlengine integration: split docs into code, divide api docs
Still more to be done in object_mapper.rst cqlengine
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
81
docs/api/cassandra/cqlengine/columns.rst
Normal file
81
docs/api/cassandra/cqlengine/columns.rst
Normal 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)
|
||||
215
docs/api/cassandra/cqlengine/models.rst
Normal file
215
docs/api/cassandra/cqlengine/models.rst
Normal 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
|
||||
42
docs/api/cassandra/cqlengine/query.rst
Normal file
42
docs/api/cassandra/cqlengine/query.rst
Normal 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
108
docs/cqlengine/batches.rst
Normal 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)
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
===================
|
||||
|
||||
|
||||
142
docs/index.rst
142
docs/index.rst
@@ -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
102
docs/object_mapper.rst
Normal 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
|
||||
Reference in New Issue
Block a user