Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
linkyndy committed Dec 15, 2014
2 parents 862cd21 + e0b13a2 commit 260b39d
Show file tree
Hide file tree
Showing 13 changed files with 867 additions and 300 deletions.
27 changes: 17 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class City(Model):

romania = Country.create(name='Romania')
romania['cities'].add(City(name='Timisoara'), City(name='Bucharest'))
print len(list(romania['cities'].all())) # prints 2
print romania['cities'].count() # prints 2
```

#### Has and belongs to many
Expand All @@ -114,7 +114,7 @@ my_post = Post.create(name='My first post')
personal_tag = Tag.create(name='personal')
public_tag = Tag.create(name='public')
my_post['tags'].add(personal_tag, public_tag)
print len(list(my_post['tags'].all())) # prints 2
print my_post['tags'].count() # prints 2
```

#### Has many through
Expand Down Expand Up @@ -145,7 +145,7 @@ class Celebrity(Model):

Celebrity.create(name='george clooney')
Celebrity.create(name='kate winslet')
upper = Celebrity.table.map({'name': r.row['name'].upcase()}).run()
upper = Celebrity.map({'name': r.row['name'].upcase()}).run()
print list(upper) # prints [{u'name': u'GEORGE CLOONEY'}, {u'name': u'KATE WINSLET'}]
```

Expand All @@ -164,18 +164,19 @@ jack.is_minor() # prints True
### Custom class methods

```python
from remodel.decorators import classaccessonly
from remodel.related import ObjectSet
from remodel.object_handler import ObjectHandler, ObjectSet

class TripObjectHandler(ObjectHandler):
def in_europe(self):
return ObjectSet(self, self.query.filter({'continent': 'Europe'}))

class Trip(Model):
@classaccessonly
def in_europe(cls):
return ObjectSet(cls, cls.table.filter({'continent': 'Europe'}).run())
object_handler = TripObjectHandler

Trip.create(continent='Europe', city='Paris')
Trip.create(continent='Asia', city='Shanghai')
Trip.create(continent='Europe', city='Dublin')
print len(list(Trip.in_europe())) # prints 2
print len(Trip.in_europe()) # prints 2
```

### Viewing object fields
Expand Down Expand Up @@ -242,7 +243,13 @@ Remodel is under active development and it is not yet production-ready. Any cont

### How to contribute

Just fork this repository, do your magic and submit a pull request!
Just fork this repository, do your magic and submit a pull request on the development branch!

### Branches

Active development and up-to-date code can be found on the `develop` branch

Stable code can be found on the `master` branch

### Running tests

Expand Down
8 changes: 4 additions & 4 deletions remodel/field_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import models
from registry import index_registry
from related import (HasOneDescriptor, BelongsToDescriptor, HasManyDescriptor,
HasAndBelongsToManyDescriptor, ObjectSet)
from utils import pluralize
HasAndBelongsToManyDescriptor)
from utils import tableize


class FieldHandlerBase(type):
Expand Down Expand Up @@ -40,7 +40,7 @@ def __new__(cls, name, bases, dct):
other, field, lkey, rkey = rel
else:
other = rel
field, lkey, rkey = pluralize(other.lower()), 'id', '%s_id' % model.lower()
field, lkey, rkey = tableize(other), 'id', '%s_id' % model.lower()
dct[field] = HasManyDescriptor(other, lkey, rkey)
dct['related'].add(field)
index_registry.register(other, rkey)
Expand All @@ -49,7 +49,7 @@ def __new__(cls, name, bases, dct):
other, field, lkey, rkey = rel
else:
other = rel
field, lkey, rkey = pluralize(other.lower()), 'id', 'id'
field, lkey, rkey = tableize(other), 'id', 'id'
join_model = '_' + ''.join(sorted([model, other]))
try:
models.ModelBase(join_model, (models.Model,), {})
Expand Down
59 changes: 16 additions & 43 deletions remodel/models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import rethinkdb as r

from decorators import classproperty, classaccessonly, classaccessonlyproperty
from decorators import classaccessonlyproperty
from errors import OperationError
from field_handler import FieldHandlerBase, FieldHandler
from object_handler import ObjectHandler
from registry import model_registry
from related import ObjectSet
from utils import wrap_document, pluralize
from utils import deprecation_warning, tableize


REL_TYPES = ('has_one', 'has_many', 'belongs_to', 'has_and_belongs_to_many')
Expand All @@ -21,18 +21,25 @@ def __new__(cls, name, bases, dct):
return super_new(cls, name, bases, dct)

# Set metadata
dct['_table'] = pluralize(name).lower()
dct['_table'] = tableize(name)

rel_attrs = {rel: dct.setdefault(rel, ()) for rel in REL_TYPES}
dct['_field_handler_cls'] = FieldHandlerBase(
'%sFieldHandler' % name,
(FieldHandler,),
dict(rel_attrs, model=name))

object_handler_cls = dct.setdefault('object_handler', ObjectHandler)

new_class = super_new(cls, name, bases, dct)
model_registry.register(name, new_class)
setattr(new_class, 'objects', object_handler_cls(new_class))
return new_class

# Proxies undefined attributes to Model.objects; useful for building
# ReQL queries directly on the Model (e.g.: User.order_by('name').run())
def __getattr__(cls, name):
return getattr(cls.objects, name)


class Model(object):
__metaclass__ = ModelBase
Expand Down Expand Up @@ -80,43 +87,6 @@ def delete(self):
for field in self.fields.related:
delattr(self.fields, field)

@classaccessonly
def create(cls, **kwargs):
obj = cls(**kwargs)
obj.save()
return obj

@classaccessonly
def get(cls, id_=None, **kwargs):
if id_:
doc = cls.table.get(id_).run()
if doc is not None:
return wrap_document(cls, doc)
return None
try:
return list(ObjectSet(cls, (cls.table.filter(kwargs)
.limit(1).run())))[0]
except IndexError:
return None

@classaccessonly
def get_or_create(cls, id_=None, **kwargs):
obj = cls.get(id_, **kwargs)
if obj:
return obj, False
return cls.create(**kwargs), True

@classaccessonly
def all(cls):
return ObjectSet(cls, cls.table.run())

@classaccessonly
def filter(cls, ids=None, **kwargs):
if ids:
return ObjectSet(cls, (cls.table.get_all(r.args(ids))
.filter(kwargs).run()))
return ObjectSet(cls, cls.table.filter(kwargs).run())

def __getitem__(self, key):
try:
return getattr(self.fields, key)
Expand Down Expand Up @@ -150,4 +120,7 @@ def __str__(self):

@classaccessonlyproperty
def table(cls):
return r.table(cls._table)
deprecation_warning('Model.table will be deprecated soon. Please use '
'Model.objects to build any custom query on a '
'Model\'s table (read more about ObjectHandler)')
return cls.objects
90 changes: 90 additions & 0 deletions remodel/object_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import rethinkdb as r


class ObjectHandler(object):
def __init__(self, model_cls, query=None):
self.model_cls = model_cls
self.query = query or r.table(model_cls._table)

def __getattr__(self, name):
return getattr(self.query, name)

def all(self):
return ObjectSet(self, self.query)

def create(self, **kwargs):
obj = self.model_cls(**kwargs)
obj.save()
return obj

def get(self, id_=None, **kwargs):
if id_:
try:
doc = self.query.get(id_).run()
except AttributeError:
# self.query has a get_all applied, cannot call get
kwargs.update(id=id_)
else:
if doc is not None:
return self._wrap(doc)
return None
docs = self.query.filter(kwargs).limit(1).run()
try:
return self._wrap(list(docs)[0])
except IndexError:
return None

def get_or_create(self, id_=None, **kwargs):
obj = self.get(id_, **kwargs)
if obj:
return obj, False
return self.create(**kwargs), True

def filter(self, ids=None, **kwargs):
if ids:
try:
query = self.query.get_all(r.args(ids)).filter(kwargs)
except AttributeError:
# self.query already has a get_all applied
query = (self.query.filter(lambda doc: r.expr(ids).contains(doc['id']))
.filter(kwargs))
else:
query = self.query.filter(kwargs)
return ObjectSet(self, query)

def count(self):
return self.query.count().run()

def _wrap(self, doc):
obj = self.model_cls()
# Assign fields this way to skip validation
obj.fields.__dict__ = doc
return obj


class ObjectSet(object):
def __init__(self, object_handler, query):
self.object_handler = object_handler
self.query = query
self.result_cache = None

def __iter__(self):
self._fetch_results()
return iter(self.result_cache)

def __len__(self):
self._fetch_results()
return len(self.result_cache)

def __getitem__(self, key):
self._fetch_results()
return self.result_cache[key]

def iterator(self):
results = self.query.run()
for doc in results:
yield self.object_handler._wrap(doc)

def _fetch_results(self):
if self.result_cache is None:
self.result_cache = list(self.iterator())
Loading

0 comments on commit 260b39d

Please sign in to comment.