def update(self, conn, **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>` :param conn: Cassandra connection wrapper used to execute the query. :type: cqlengine.ConnectionInterface subclass :param kwargs: Model column values as keyword arguments. :return: self """ for k, v in values.items(): col = self._columns.get(k) # check for nonexistant columns if col is None: raise ValidationError( "{0}.{1} has no column named: {2}".format( self.__module__, self.__class__.__name__, k, )) # check for primary key update attempts if col.is_primary_key: raise ValidationError( "Cannot apply update to primary key '{0}' for " "{1}.{2}".format( k, self.__module__, self.__class__.__name__, )) setattr(self, k, v) self.validate() q = query.UpdateDMLQuery(self.__class__, self, ttl=self._ttl, timestamp=self._timestamp, consistency=self.__consistency__, conditional=self._conditional, timeout=self._timeout, if_exists=self._if_exists) self._execute_query(conn, q) self._set_persisted() self._timestamp = None return self
def validate(self, value): val = super(List, self).validate(value) if val is None: return if not isinstance(val, (set, list, tuple)): raise ValidationError('{0} {1} is not a list object'.format( self.column_name, val)) if None in val: raise ValidationError("{0} None is not allowed in a list".format( self.column_name)) return [self.value_col.validate(v) for v in val]
def validate(self, value): val = super(Map, self).validate(value) if val is None: return if not isinstance(val, (dict, util.OrderedMap)): raise ValidationError('{0} {1} is not a dict object'.format( self.column_name, val)) if None in val: raise ValidationError("{0} None is not allowed in a map".format( self.column_name)) # TODO: stop doing this conversion because it doesn't support non-hashable collections as keys (cassandra does) # will need to start using the cassandra.util types in the next major rev (PYTHON-494) return dict((self.key_col.validate(k), self.value_col.validate(v)) for k, v in val.items())
def __init__(self, value): """ :param value: the time to create bounding time uuid from :type value: datetime """ if not isinstance(value, datetime): raise ValidationError("datetime instance is required") super(TimeUUIDQueryFunction, self).__init__(value)
def validate(self, value): value = super(Text, self).validate(value) if not isinstance(value, (six.string_types, bytearray)) and value is not None: raise ValidationError('{0} {1} is not a string'.format( self.column_name, type(value))) if self.max_length is not None: if value and len(value) > self.max_length: raise ValidationError( '{0} is longer than {1} characters'.format( self.column_name, self.max_length)) if self.min_length: if (self.min_length and not value) or len(value) < self.min_length: raise ValidationError( '{0} is shorter than {1} characters'.format( self.column_name, self.min_length)) return value
def validate(self, value): value = super(BaseCollectionColumn, self).validate(value) # It is dangerous to let collections have more than 65535. # See: https://issues.apache.org/jira/browse/CASSANDRA-5428 if value is not None and len(value) > 65535: raise ValidationError( "{0} Collection can't have more than 65535 elements.".format( self.column_name)) return value
def validate(self, value): value = super(BaseFloat, self).validate(value) if value is None: return try: return float(value) except (TypeError, ValueError): raise ValidationError("{0} {1} is not a valid float".format( self.column_name, value))
def validate(self, value): val = super(Tuple, self).validate(value) if val is None: return if len(val) > len(self.types): raise ValidationError( "Value %r has more fields than tuple definition (%s)" % (val, ', '.join(t for t in self.types))) return tuple(t.validate(v) for t, v in zip(self.types, val))
def __init__(self, types, **kwargs): """ :param types: a sequence of sub types in this collection """ instances = [] for t in types: inheritance_comparator = issubclass if isinstance( t, type) else isinstance if not inheritance_comparator(t, Column): raise ValidationError("%s is not a column class" % (t, )) if t.db_type is None: raise ValidationError("%s is an abstract type" % (t, )) inst = t() if isinstance(t, type) else t if isinstance(t, BaseCollectionColumn): inst._freeze_db_type() instances.append(inst) self.types = instances super(BaseCollectionColumn, self).__init__(**kwargs)
def validate(self, value): val = super(VarInt, self).validate(value) if val is None: return try: return int(val) except (TypeError, ValueError): raise ValidationError( "{0} {1} can't be converted to integral value".format( self.column_name, value))
def validate(self, value): """ Returns a cleaned and validated value. Raises a ValidationError if there's a problem """ if value is None: if self.required: raise ValidationError( '{0} - None values are not allowed'.format( self.column_name or self.db_field)) return value
def create(cls, conn, **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: {0}".format(extra_columns)) return cls.objects.create(conn, **kwargs)
def validate(self, value): val = super(Set, self).validate(value) if val is None: return types = (set, util.SortedSet) if self.strict else (set, util.SortedSet, list, tuple) if not isinstance(val, types): if self.strict: raise ValidationError('{0} {1} is not a set object'.format( self.column_name, val)) else: raise ValidationError( '{0} {1} cannot be coerced to a set object'.format( self.column_name, val)) if None in val: raise ValidationError("{0} None not allowed in a set".format( self.column_name)) # TODO: stop doing this conversion because it doesn't support non-hashable collections as keys (cassandra does) # will need to start using the cassandra.util types in the next major rev (PYTHON-494) return set(self.value_col.validate(v) for v in val)
def validate(self, value): from decimal import Decimal as _Decimal from decimal import InvalidOperation val = super(Decimal, self).validate(value) if val is None: return try: return _Decimal(repr(val)) if isinstance(val, float) else _Decimal(val) except InvalidOperation: raise ValidationError( "{0} '{1}' can't be coerced to decimal".format( self.column_name, val))
def create(cls, conn, **kwargs): """Create an instance of this model in the database. :param conn: Cassandra connection wrapper used to execute the query. :type: cqlengine.ConnectionInterface subclass :param kwargs: Model column values as keyword arguments. :return: The instance created. """ extra_columns = set(kwargs.keys()) - set(cls._columns.keys()) if extra_columns: raise ValidationError( "Incorrect columns passed: {0}".format(extra_columns)) return cls.objects.create(conn, **kwargs)
def validate(self, value): val = super(UUID, self).validate(value) if val is None: return if isinstance(val, _UUID): return val if isinstance(val, six.string_types): try: return _UUID(val) except ValueError: # fall-through to error pass raise ValidationError("{0} {1} is not a valid uuid".format( self.column_name, value))
def __init__(self, value_type, strict=True, default=set, **kwargs): """ :param value_type: a column class indicating the types of the value :param strict: sets whether non set values will be coerced to set type on validation, or raise a validation error, defaults to True """ self.strict = strict super(Set, self).__init__((value_type, ), default=default, **kwargs) self.value_col = self.types[0] if not self.value_col._python_type_hashable: raise ValidationError( "Cannot create a Set with unhashable value type (see PYTHON-494)" ) self.db_type = 'set<{0}>'.format(self.value_col.db_type)
def to_database(self, value): value = super(DateTime, self).to_database(value) if value is None: return if not isinstance(value, datetime): if isinstance(value, date): value = datetime(value.year, value.month, value.day) else: raise ValidationError( "{0} '{1}' is not a datetime object".format( self.column_name, value)) epoch = datetime(1970, 1, 1, tzinfo=value.tzinfo) offset = get_total_seconds( epoch.tzinfo.utcoffset(epoch)) if epoch.tzinfo else 0 return int((get_total_seconds(value - epoch) - offset) * 1000)
def validate(self, value): """ Only allow ASCII and None values. Check against US-ASCII, a.k.a. 7-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set. Source: https://github.com/apache/cassandra/blob /3dcbe90e02440e6ee534f643c7603d50ca08482b/src/java/org/apache/cassandra /serializers/AsciiSerializer.java#L29 """ value = super(Ascii, self).validate(value) if value: charset = value if isinstance(value, (bytearray, )) else map(ord, value) if not set(range(128)).issuperset(charset): raise ValidationError( '{!r} is not an ASCII string.'.format(value)) return value
def __init__(self, key_type, value_type, default=dict, **kwargs): """ :param key_type: a column class indicating the types of the key :param value_type: a column class indicating the types of the value """ super(Map, self).__init__((key_type, value_type), default=default, **kwargs) self.key_col = self.types[0] self.value_col = self.types[1] if not self.key_col._python_type_hashable: raise ValidationError( "Cannot create a Map with unhashable key type (see PYTHON-494)" ) self.db_type = 'map<{0}, {1}>'.format(self.key_col.db_type, self.value_col.db_type)
def update(self, conn, **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 (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 nulled_columns = set() updated_columns = set() us = UpdateStatement( self.column_family_name, where=self._where, ttl=self._ttl, timestamp=self._timestamp, conditionals=self._conditional, if_exists=self._if_exists, ) for name, val in values.items(): col_name, col_op = self._parse_filter_arg(name) col = self.model._columns.get(col_name) # check for nonexistant columns if col is None: raise ValidationError( "{0}.{1} has no column named: {2}".format( self.__module__, self.model.__name__, col_name ) ) # check for primary key update attempts if col.is_primary_key: raise ValidationError( "Cannot apply update to primary key '{0}' for " "{1}.{2}".format( col_name, self.__module__, self.model.__name__, ) ) # we should not provide default values in this use case. val = col.validate(val) if val is None: nulled_columns.add(col_name) continue us.add_update(col, val, operation=col_op) updated_columns.add(col_name) if us.assignments: self._execute_statement(conn, us) if nulled_columns: delete_conditional = [ condition for condition in self._conditional if condition.field not in updated_columns ] if self._conditional else None ds = DeleteStatement( self.column_family_name, fields=nulled_columns, where=self._where, conditionals=delete_conditional, if_exists=self._if_exists, ) self._execute_statement(conn, ds)