diff --git a/README.md b/README.md index 0e174ce9..13652341 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,22 @@ cassandraengine =============== -Django ORM / Mongoengine style ORM for Cassandra +Python Cassandra ORM in the style of django / mongoengine In it's current state you can define column families, create and delete column families based on your model definiteions, save models and retrieve models by their primary keys. -That's about it. Also, there are only 2 tests and the CQL stuff is very simplistic at this point. +That's about it. Also, the CQL stuff is pretty simple at this point. ##TODO +* Complex queries (class Q(object)) +* Match column names to mongoengine field names? +* mongoengine fields? URLField, EmbeddedDocument, ListField, DictField +* column ttl? +* ForeignKey/DBRef fields? * dynamic column support -* return None when row isn't found in find() * tests * query functionality * nice column and model class __repr__ + + diff --git a/cassandraengine/columns.py b/cassandraengine/columns.py index aa7a4b35..67f697ea 100644 --- a/cassandraengine/columns.py +++ b/cassandraengine/columns.py @@ -106,6 +106,9 @@ class Integer(BaseColumn): class DateTime(BaseColumn): db_type = 'timestamp' + def __init__(self, **kwargs): + super(DateTime, self).__init__(**kwargs) + raise NotImplementedError class UUID(BaseColumn): """ @@ -153,4 +156,23 @@ class Float(BaseColumn): class Decimal(BaseColumn): db_type = 'decimal' #TODO: this + def __init__(self, **kwargs): + super(DateTime, self).__init__(**kwargs) + raise NotImplementedError +class Counter(BaseColumn): + def __init__(self, **kwargs): + super(DateTime, self).__init__(**kwargs) + raise NotImplementedError + +#TODO: research supercolumns +#http://wiki.apache.org/cassandra/DataModel +class List(BaseColumn): + def __init__(self, **kwargs): + super(DateTime, self).__init__(**kwargs) + raise NotImplementedError + +class Dict(BaseColumn): + def __init__(self, **kwargs): + super(DateTime, self).__init__(**kwargs) + raise NotImplementedError diff --git a/cassandraengine/manager.py b/cassandraengine/manager.py index 9dc501b2..3f7ec7f6 100644 --- a/cassandraengine/manager.py +++ b/cassandraengine/manager.py @@ -21,17 +21,20 @@ class Manager(object): #trim to less than 48 characters or cassandra will complain cf_name = cf_name[-48:] return cf_name - - def column_family_definition(self): + + def __call__(self, **kwargs): """ - Generates a definition used for tale creation + filter shortcut """ + return self.filter(**kwargs) def find(self, pk): """ Returns the row corresponding to the primary key value given """ values = QuerySet(self.model).find(pk) + if values is None: return + #change the column names to model names #in case they are different field_dict = {} @@ -55,6 +58,10 @@ class Manager(object): def create(self, **kwargs): return self.model(**kwargs).save() + def delete(self, **kwargs): + pass + + #----single instance methods---- def _save_instance(self, instance): """ The business end of save, this is called by the models @@ -63,18 +70,15 @@ class Manager(object): """ QuerySet(self.model).save(instance) + def _delete_instance(self, instance): + """ + Deletes a single instance + """ + QuerySet(self.model).delete_instance(instance) + + #----column family create/delete---- def _create_column_family(self): QuerySet(self.model)._create_column_family() def _delete_column_family(self): QuerySet(self.model)._delete_column_family() - - def delete(self, **kwargs): - pass - - def __call__(self, **kwargs): - """ - filter shortcut - """ - return self.filter(**kwargs) - diff --git a/cassandraengine/models.py b/cassandraengine/models.py index 11500896..040682d4 100644 --- a/cassandraengine/models.py +++ b/cassandraengine/models.py @@ -65,7 +65,8 @@ class BaseModel(object): return self def delete(self): - pass + """ Deletes this instance """ + self.objects._delete_instance(self) class ModelMetaClass(type): diff --git a/cassandraengine/query.py b/cassandraengine/query.py index 76149f63..025d135a 100644 --- a/cassandraengine/query.py +++ b/cassandraengine/query.py @@ -42,7 +42,7 @@ class QuerySet(object): """ cur = self._cursor values = cur.fetchone() - if values is None: return None + if values is None: return names = [i[0] for i in cur.description] value_dict = dict(zip(names, values)) return value_dict @@ -64,15 +64,12 @@ class QuerySet(object): return QuerySet(self.model, query_args=qargs) def exclude(self, **kwargs): - """ - Need to invert the logic for all kwargs - """ + """ Need to invert the logic for all kwargs """ pass def count(self): - """ - Returns the number of rows matched by this query - """ + """ Returns the number of rows matched by this query """ + qs = 'SELECT COUNT(*) FROM {}'.format(self.column_family_name) def find(self, pk): """ @@ -128,6 +125,23 @@ class QuerySet(object): cur = conn.cursor() cur.execute(qs, field_values) + #----delete--- + def delete(self): + """ + Deletes the contents of a query + """ + + def delete_instance(self, instance): + """ Deletes one instance """ + pk_name = self.model._pk_name + qs = ['DELETE FROM {}'.format(self.column_family_name)] + qs += ['WHERE {0}=:{0}'.format(pk_name)] + qs = ' '.join(qs) + + conn = get_connection() + cur = conn.cursor() + cur.execute(qs, {pk_name:instance.pk}) + def _create_column_family(self): #construct query string qs = ['CREATE TABLE {}'.format(self.column_family_name)] diff --git a/cassandraengine/tests/model/test_model_io.py b/cassandraengine/tests/model/test_model_io.py index 7680a789..8adf4691 100644 --- a/cassandraengine/tests/model/test_model_io.py +++ b/cassandraengine/tests/model/test_model_io.py @@ -38,6 +38,15 @@ class TestModelIO(BaseCassEngTestCase): tm2 = TestModel.objects.find(tm.pk) self.assertEquals(tm.count, tm2.count) + def test_model_deleting_works_properly(self): + """ + Tests that an instance's delete method deletes the instance + """ + tm = TestModel.objects.create(count=8, text='123456789') + tm.delete() + tm2 = TestModel.objects.find(tm.pk) + self.assertIsNone(tm2) + def test_nullable_columns_are_saved_properly(self): """ Tests that nullable columns save without any trouble