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()