Hi,

I attached a new patch.
I also attached a file with a couple of tests I ran. (based on test_dal.py)

Needs more testing by other GAE users though.

Quint

On Monday, October 28, 2013 2:49:37 PM UTC+1, Massimo Di Pierro wrote:
>
> If you send me a revised patch to do this, I will include. It may be 
> better than using a different adapter.
>
> On Monday, 28 October 2013 01:40:41 UTC-5, Quint wrote:
>>
>> Yes, using the same adapter with a parameter is also a possibility i 
>> thought of. Don't really know anymore why i choose this. Maybe because 
>> initially i wanted to create a plugable thingy without changing the DAL 
>> code.
>
>

-- 
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
--- 
You received this message because you are subscribed to the Google Groups 
"web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.
270a271
>     from google.appengine.ext import ndb
273a275
>     from google.appengine.ext.ndb.polymodel import PolyModel as NDBPolyModel
517a520,552
>     #TODO Needs more testing
>     class NDBDecimalProperty(ndb.StringProperty):
>         """
>         NDB decimal implementation
>         """
>         data_type = decimal.Decimal
> 
>         def __init__(self, precision, scale, **kwargs):
>             d = '1.'
>             for x in range(scale):
>                 d += '0'
>             self.round = decimal.Decimal(d)
> 
>         def _to_base_type(self, value):
>             if value is None or value == '':
>                 return None
>             else:
>                 return str(value)
> 
>         def _from_base_type(self, value):
>             if value is None or value == '':
>                 return None
>             else:
>                 return decimal.Decimal(value).quantize(self.round)
> 
>         def _validate(self, value):
>             if value is None or isinstance(value, decimal.Decimal):
>                 return value
>             elif isinstance(value, basestring):
>                 return decimal.Decimal(value)
>             raise TypeError("Property %s must be a Decimal or string."\
>                                         % self._name)
> 
2121c2156,2159
<                             id = value.key().id_or_name()
---
>                             if self.use_ndb:
>                                 id = value.key.id()
>                             else:
>                                 id = value.key().id_or_name()
4627a4666,4678
>     """
>     NDB:
> 
>     You can enable NDB by using adapter_args:
> 
>     db = DAL('google:datastore', adapter_args={'ndb_settings':ndb_settings, 'use_ndb':True})
> 
>     ndb_settings is optional and can be used for per model caching settings.
>     It must be a dict in this form:
>     ndb_settings = {<table_name>:{<variable_name>:<variable_value>}}
>     See: https://developers.google.com/appengine/docs/python/ndb/cache
>     """
> 
4640c4691,4716
<         self.types.update({
---
>         self.use_ndb = ('use_ndb' in adapter_args) and adapter_args['use_ndb']
>         if self.use_ndb is True:
>             self.types.update({
>                 'boolean': ndb.BooleanProperty,
>                 'string': (lambda **kwargs: ndb.StringProperty(**kwargs)),
>                 'text': ndb.TextProperty,
>                 'json': ndb.TextProperty,
>                 'password': ndb.StringProperty,
>                 'blob': ndb.BlobProperty,
>                 'upload': ndb.StringProperty,
>                 'integer': ndb.IntegerProperty,
>                 'bigint': ndb.IntegerProperty,
>                 'float': ndb.FloatProperty,
>                 'double': ndb.FloatProperty,
>                 'decimal': NDBDecimalProperty,
>                 'date': ndb.DateProperty,
>                 'time': ndb.TimeProperty,
>                 'datetime': ndb.DateTimeProperty,
>                 'id': None,
>                 'reference': ndb.IntegerProperty,
>                 'list:string': (lambda **kwargs: ndb.StringProperty(repeated=True,default=None, **kwargs)),
>                 'list:integer': (lambda **kwargs: ndb.IntegerProperty(repeated=True,default=None, **kwargs)),
>                 'list:reference': (lambda **kwargs: ndb.IntegerProperty(repeated=True,default=None, **kwargs)),
>                 })
>         else:
>             self.types.update({
4672a4749,4753
>         self.keyfunc = (self.use_ndb and ndb.Key) or Key.from_path
> 
>         self.ndb_settings = None
>         if 'ndb_settings' in adapter_args:
>             self.ndb_settings = adapter_args['ndb_settings']
4689c4770
<             elif isinstance(field_type, gae.Property):
---
>             elif isinstance(field_type, ((self.use_ndb and ndb.Property) or gae.Property)):
4697c4778,4779
<                 ftype = GAEDecimalProperty(precision, scale, **attr)
---
>                 dec_cls = (self.use_ndb and NDBDecimalProperty) or GAEDecimalProperty
>                 ftype = dec_cls(precision, scale, **attr)
4717c4799,4805
<             table._tableobj = classobj(table._tablename, (gae.Model, ), myfields)
---
>             model_cls = (self.use_ndb and ndb.Model) or gae.Model
>             table._tableobj =  classobj(table._tablename, (model_cls, ), myfields)
>             if self.use_ndb:
>                 # Set NDB caching variables
>                 if self.ndb_settings and (table._tablename in self.ndb_settings):
>                     for k, v in self.ndb_settings.iteritems():
>                         setattr(table._tableobj, k, v)
4719c4807,4808
<             table._tableobj = classobj(table._tablename, (PolyModel, ), myfields)
---
>             pm_cls = (self.use_ndb and NDBPolyModel) or PolyModel
>             table._tableobj = classobj(table._tablename, (pm_cls, ), myfields)
4832a4922,4931
>     def filter(self, query, tableobj, prop, op, value):
>         return {
>             '=': query.filter(getattr(tableobj, prop) == value),
>             '>': query.filter(getattr(tableobj, prop) > value),
>             '<': query.filter(getattr(tableobj, prop) < value),
>             '<=': query.filter(getattr(tableobj, prop) <= value),
>             '>=': query.filter(getattr(tableobj, prop) >= value),
>             '!=': query.filter(getattr(tableobj, prop) != value),
>         }[op]
> 
4857c4956
<         #tableobj is a GAE Model class (or subclass)
---
>         #tableobj is a GAE/NDB Model class (or subclass)
4889c4988,4992
<         items = gae.Query(tableobj, projection=query_projection,
---
>         if self.use_ndb:
>             qo = ndb.QueryOptions(projection=query_projection, cursor=cursor)
>             items = tableobj.query(default_options=qo)
>         else:
>             items = gae.Query(tableobj, projection=query_projection,
4903c5006
<                 elif isinstance(filter.value, Key):
---
>                 elif isinstance(filter.value, (self.use_ndb and ndb.Key) or Key):
4907c5010
<                     item = tableobj.get(filter.value)
---
>                     item = (self.use_ndb and filter.value.get()) or tableobj.get(filter.value)
4920,4922c5023,5029
<                     items.order('__key__')
<                 items = items.filter('%s %s' % (filter.name,filter.op),
<                                      filter.value)
---
>                     if self.use_ndb:
>                         items.order(tableobj._key)
>                     else:
>                         items.order('__key__')
>                 items = (self.use_ndb and self.filter(items, tableobj, filter.name, filter.op, filter.value)) or\
>                         items.filter('%s %s' % (filter.name,filter.op), filter.value)
> 
4937,4938c5044,5057
<                     order={'-id':'-__key__','id':'__key__'}.get(order,order)
<                     items = items.order(order)
---
>                     if self.use_ndb:
>                         #TODO There must be a better way
>                         def make_order(o):
>                             s = str(o)
>                             desc = s[0] == '-'
>                             s = (desc and s[1:]) or s
>                             return  (desc and  -getattr(tableobj, s)) or getattr(tableobj, s)
>                         _order = {'-id':-tableobj._key,'id':tableobj._key}.get(order)
>                         if _order is None:
>                             _order = make_order(order)
>                         items = items.order(_order)
>                     else:
>                         order={'-id':'-__key__','id':'__key__'}.get(order,order)
>                         items = items.order(order)
4942c5061,5064
<                 rows = items.fetch(limit,offset=offset)
---
>                 if self.use_ndb:
>                     rows, cursor, more = items.fetch_page(limit,offset=offset)
>                 else:
>                     rows =  items.fetch(limit,offset=offset)
4946c5068
<                     db['_lastcursor'] = items.cursor()
---
>                     db['_lastcursor'] = (self.use_ndb and cursor) or items.cursor()
5009c5131,5134
<                 gae.delete(leftitems)
---
>                 if self.use_ndb:
>                     ndb.delete_multi(leftitems)
>                 else:
>                     gae.delete(leftitems)
5013c5138,5141
<             gae.delete(items)
---
>             if self.use_ndb:
>                 ndb.delete_multi([item.key for item in items])
>             else:
>                 gae.delete(items)
5033,5034c5161,5163
<         rid = Reference(tmp.key().id())
<         (rid._table, rid._record, rid._gaekey) = (table, None, tmp.key())
---
>         key = (self.use_ndb and tmp.key) or tmp.key()
>         rid = Reference(key.id())
>         (rid._table, rid._record, rid._gaekey) = (table, None, key)
5042c5171,5174
<         gae.put(parsed_items)
---
>         if self.use_ndb:
>             ndb.put_multi(parsed_items)
>         else:
>             gae.put(parsed_items)
import unittest
import glob
import os
from google.appengine.ext import testbed
from gluon.dal import DAL, Field

def tearDownModule():
    if os.path.isfile('sql.log'):
        os.unlink('sql.log')
    for a in glob.glob('*.table'):
        os.unlink(a)

class TestDbBase(unittest.TestCase):
    def setUp (self):
        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub()
        self.testbed.init_memcache_stub()

        #self.db = DAL('google:datastore', adapter_args={'use_ndb':True})
        #self.db = DAL('google:datastore')

        ndb_settings = {'tt':{'_use_cache': False,
                              '_use_memcache': False}}

        self.db = DAL('google:datastore', adapter_args={'ndb_settings':ndb_settings, 'use_ndb':True})

    def tearDown (self):
        self.testbed.deactivate()

class TestInsert(TestDbBase):

    def testRun(self):
        db = self.db
        db.define_table('tt', Field('aa'))
        self.assertEqual(db.tt.insert(aa='1'), 1)
        self.assertEqual(db.tt.insert(aa='1'), 2)
        self.assertEqual(db.tt.insert(aa='1'), 3)
        self.assertEqual(db(db.tt.aa == '1').count(), 3)
        self.assertEqual(db(db.tt.aa == '2').isempty(), True)
        self.assertEqual(db(db.tt.aa == '1').update(aa='2'), 3)
        self.assertEqual(db(db.tt.aa == '2').count(), 3)
        self.assertEqual(db(db.tt.aa == '2').isempty(), False)
        self.assertEqual(db(db.tt.aa == '2').delete(), 3)
        self.assertEqual(db(db.tt.aa == '2').isempty(), True)
        #db.tt.drop()

class TestDecimal(TestDbBase):

    def testRun(self):
        db = self.db
        db.define_table('tt', Field('aa', 'decimal(10,4)'))
        self.assertEqual(db.tt.insert(aa=1), 1)
        self.assertEqual(db.tt.insert(aa=1), 2)
        self.assertEqual(db.tt.insert(aa=1), 3)
        self.assertEqual(db(db.tt.aa == '1').count(), 3)
        self.assertEqual(db(db.tt.aa == '2').isempty(), True)
        self.assertEqual(db(db.tt.aa == '1').update(aa=2), 3)
        self.assertEqual(db(db.tt.aa == '2').count(), 3)
        self.assertEqual(db(db.tt.aa == '2').isempty(), False)
        self.assertEqual(db(db.tt.aa == '2').delete(), 3)
        self.assertEqual(db(db.tt.aa == '2').isempty(), True)
        #db.tt.drop()

class TestSelect(TestDbBase):

    def testRun(self):
        db = self.db
        db.define_table('tt', Field('aa'))
        self.assertEqual(db.tt.insert(aa='1'), 1)
        self.assertEqual(db.tt.insert(aa='2'), 2)
        self.assertEqual(db.tt.insert(aa='3'), 3)
        self.assertEqual(db(db.tt.id > 0).count(), 3)
        self.assertEqual(db(db.tt.id > 0).select(orderby=db.tt.aa | db.tt.id)[0].aa, '1')
        self.assertEqual(db(db.tt.id > 0).select(orderby=~db.tt.aa | db.tt.id)[0].aa, '3')
        self.assertEqual(len(db(db.tt.id > 0).select(limitby=(1, 2))), 1)
        self.assertEqual(db(db.tt.id > 0).select(limitby=(1, 2))[0].aa, '2')
        self.assertEqual(len(db().select(db.tt.ALL)), 3)
        self.assertEqual(db(db.tt.aa == None).count(), 0)
        self.assertEqual(db(db.tt.aa != None).count(), 3)
        self.assertEqual(db(db.tt.aa > '1').count(), 2)
        self.assertEqual(db(db.tt.aa >= '1').count(), 3)
        self.assertEqual(db(db.tt.aa == '1').count(), 1)
        self.assertEqual(db(db.tt.aa != '1').count(), 2)
        self.assertEqual(db(db.tt.aa < '3').count(), 2)
        self.assertEqual(db(db.tt.aa <= '3').count(), 3)
        self.assertEqual(db(db.tt.aa > '1')(db.tt.aa < '3').count(), 1)
        self.assertEqual(db((db.tt.aa > '1') & (db.tt.aa < '3')).count(), 1)
        #self.assertEqual(db((db.tt.aa > '1') | (db.tt.aa < '3')).count(), 3) # Not supported on GAE
        self.assertEqual(db((db.tt.aa > '1') & ~(db.tt.aa > '2')).count(), 1)
        self.assertEqual(db(~(db.tt.aa > '1') & (db.tt.aa > '2')).count(), 0)
        #db.tt.drop()

#class TestBelongs(TestDbBase):
#
#    def testRun(self):
#        db = self.db
#        db.define_table('tt', Field('aa'))
#        self.assertEqual(db.tt.insert(aa='1'), 1)
#        self.assertEqual(db.tt.insert(aa='2'), 2)
#        self.assertEqual(db.tt.insert(aa='3'), 3)
#        self.assertEqual(db(db.tt.aa.belongs(('1', '3'))).count(), 2)
#        #self.assertEqual(db(db.tt.aa.belongs(db(db.tt.id
#        #                  > 2)._select(db.tt.aa))).count(), 1)
#        #self.assertEqual(db(db.tt.aa.belongs(db(db.tt.aa.belongs(('1',
#        #                 '3')))._select(db.tt.aa))).count(), 2)
#        #self.assertEqual(db(db.tt.aa.belongs(db(db.tt.aa.belongs(db
#        #                 (db.tt.aa.belongs(('1', '3')))._select(db.tt.aa)))._select(
#        #                 db.tt.aa))).count(), 2)
#        #db.tt.drop()

class TestContains(TestDbBase):
    def testRun(self):
        db = self.db
        db.define_table('tt', Field('aa', 'list:string'), Field('bb','string'))
        self.assertEqual(db.tt.insert(aa=['aaa','bbb'],bb='aaa'), 1)
        self.assertEqual(db.tt.insert(aa=['bbb','ddd'],bb='abb'), 2)
        self.assertEqual(db.tt.insert(aa=['eee','aaa'],bb='acc'), 3)
        self.assertEqual(db(db.tt.aa.contains('aaa')).count(), 2)
        self.assertEqual(db(db.tt.aa.contains('bbb')).count(), 2)
        self.assertEqual(db(db.tt.aa.contains('aa')).count(), 0)

        # Not supported on GAE
        #self.assertEqual(db(db.tt.bb.contains('a')).count(), 3)
        #self.assertEqual(db(db.tt.bb.contains('b')).count(), 1)
        #self.assertEqual(db(db.tt.bb.contains('d')).count(), 0)
        #self.assertEqual(db(db.tt.aa.contains(db.tt.bb)).count(), 1)
        #db.tt.drop()

class TestReference(TestDbBase):

    def testRun(self):
        db = self.db
        db.define_table('tt', Field('name'), Field('aa','reference tt'))
        db.commit()
        x = db.tt.insert(name='max')
        assert x.id == 1
        assert x['id'] == 1
        x.aa = x
        assert x.aa == 1
        x.update_record()
        y = db.tt[1]
        assert y.aa == 1
        assert y.aa.aa.aa.aa.aa.aa.name == 'max'
        z=db.tt.insert(name='xxx', aa = y)
        assert z.aa == y.id
        #db.tt.drop()
        db.commit()

if __name__ == '__main__':
    unittest.main()
    tearDownModule()

Reply via email to