Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit be41724

Browse files
Patrick Costello (pcostelloPatrick Costello (pcostello
authored andcommitted
Pushing release 1.0.11 (release for 1.9.8 SDK).
1 parent 545c45f commit be41724

File tree

8 files changed

+476
-77
lines changed

8 files changed

+476
-77
lines changed

RELEASE_NOTES

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
1+
Release 1.0.11 (included in SDK 1.9.8)
2+
3+
- Added support for more efficient query cursors.
4+
- App Engine Issue 11082: Fixed error while deserializing repeated
5+
structured properties.
6+
7+
----------------------------------------
8+
19
Release 1.0.10 (included in SDK 1.8.9)
10+
211
- Issue 239: Added support for verbose_name to ComputedProperty
312
- Issue 238: Allow deferred library to defer @ndb.toplevel.
413
- Issue 233: Fixed error for NoneType in repeated LocalStructuredProperty.
514
- Fixed deserialization inefficiency for large repeated StructuredProperty.
615

16+
----------------------------------------
17+
718
Release 1.0.9 (included in SDK 1.8.0)
819

920
- Issue 229: Add support for 'distinct' queries
1021

22+
----------------------------------------
23+
1124
Release 1.0.8 (included in SDK 1.7.7)
1225

1326
- Issue 227: ndb.Model constructor does not handle projections correctly
1427
- Issue 215: Projected entity loses projection after pickling
1528

29+
----------------------------------------
30+
1631
Release 1.0.7 (included in SDK 1.7.4)
1732

1833
- Issue 223: JsonProperty(json_type=dict) restricts the JSON value to a dict.
@@ -21,13 +36,17 @@ Release 1.0.7 (included in SDK 1.7.4)
2136
- Issue 217: Explicitly disallow deleting ComputedProperty.
2237
- Issue 213: PolyModel root class query should not filter on 'class'.
2338

39+
----------------------------------------
40+
2441
Release 1.0.6 (included in SDK 1.7.3)
2542

2643
- Issue 207: Convert dicts into structured properties automatically.
2744
- Issue 209: Save/restore datastore connection around tasklet switches.
2845
- Issue 210: Fix fetch() with offset > 1000.
2946
- Improve error message when calling Model() with positional argument(s).
3047

48+
----------------------------------------
49+
3150
Release 1.0.5 (included in SDK 1.7.2)
3251

3352
- Issue 204: Improve message for tasklet plain value error.

ndb/context.py

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -114,32 +114,85 @@ def _make_ctx_options(ctx_options, config_cls=ContextOptions):
114114

115115

116116
class AutoBatcher(object):
117+
"""Batches multiple async calls if they share the same rpc options.
118+
119+
Here is an example to explain what this class does.
120+
121+
Life of a key.get_async(options) API call:
122+
*) Key gets the singleton Context instance and invokes Context.get.
123+
*) Context.get calls Context._get_batcher.add(key, options). This
124+
returns a future "fut" as the return value of key.get_async.
125+
At this moment, key.get_async returns.
126+
127+
*) When more than "limit" number of _get_batcher.add() was called,
128+
_get_batcher invokes its self._todo_tasklet, Context._get_tasklet,
129+
with the list of keys seen so far.
130+
*) Context._get_tasklet fires a MultiRPC and waits on it.
131+
*) Upon MultiRPC completion, Context._get_tasklet passes on the results
132+
to the respective "fut" from key.get_async.
133+
134+
*) If user calls "fut".get_result() before "limit" number of add() was called,
135+
"fut".get_result() will repeatedly call eventloop.run1().
136+
*) After processing immediate callbacks, eventloop will run idlers.
137+
AutoBatcher._on_idle is an idler.
138+
*) _on_idle will run the "todo_tasklet" before the batch is full.
139+
140+
So the engine is todo_tasklet, which is a proxy tasklet that can combine
141+
arguments into batches and passes along results back to respective futures.
142+
This class is mainly a helper that invokes todo_tasklet with the right
143+
arguments at the right time.
144+
"""
117145

118146
def __init__(self, todo_tasklet, limit):
119-
# todo_tasklet is a tasklet to be called with list of (future, arg) pairs
147+
"""Init.
148+
149+
Args:
150+
todo_tasklet: the tasklet that actually fires RPC and waits on a MultiRPC.
151+
It should take a list of (future, arg) pairs and an "options" as
152+
arguments. "options" are rpc options.
153+
limit: max number of items to batch for each distinct value of "options".
154+
"""
120155
self._todo_tasklet = todo_tasklet
121-
self._limit = limit # No more than this many per callback
122-
self._queues = {} # Map options to lists of (future, arg) tuples
123-
self._running = [] # Currently running tasklets
124-
self._cache = {} # Cache of in-flight todo_tasklet futures
156+
self._limit = limit
157+
# A map from "options" to a list of (future, arg) tuple.
158+
# future is the future return from a single async operations.
159+
self._queues = {}
160+
self._running = [] # A list of in-flight todo_tasklet futures.
161+
self._cache = {} # Cache of in-flight todo_tasklet futures.
125162

126163
def __repr__(self):
127164
return '%s(%s)' % (self.__class__.__name__, self._todo_tasklet.__name__)
128165

129166
def run_queue(self, options, todo):
167+
"""Actually run the _todo_tasklet."""
130168
utils.logging_debug('AutoBatcher(%s): %d items',
131169
self._todo_tasklet.__name__, len(todo))
132-
fut = self._todo_tasklet(todo, options)
133-
self._running.append(fut)
170+
batch_fut = self._todo_tasklet(todo, options)
171+
self._running.append(batch_fut)
134172
# Add a callback when we're done.
135-
fut.add_callback(self._finished_callback, fut, todo)
173+
batch_fut.add_callback(self._finished_callback, batch_fut, todo)
136174

137175
def _on_idle(self):
176+
"""An idler eventloop can run.
177+
178+
Eventloop calls this when it has finished processing all immediate
179+
callbacks. This method runs _todo_tasklet even before the batch is full.
180+
"""
138181
if not self.action():
139182
return None
140183
return True
141184

142185
def add(self, arg, options=None):
186+
"""Adds an arg and gets back a future.
187+
188+
Args:
189+
arg: one argument for _todo_tasklet.
190+
options: rpc options.
191+
192+
Return:
193+
An instance of future, representing the result of running
194+
_todo_tasklet without batching.
195+
"""
143196
fut = tasklets.Future('%s.add(%s, %s)' % (self, arg, options))
144197
todo = self._queues.get(options)
145198
if todo is None:
@@ -171,14 +224,24 @@ def action(self):
171224
self.run_queue(options, todo)
172225
return True
173226

174-
def _finished_callback(self, fut, todo):
175-
self._running.remove(fut)
176-
err = fut.get_exception()
227+
def _finished_callback(self, batch_fut, todo):
228+
"""Passes exception along.
229+
230+
Args:
231+
batch_fut: the batch future returned by running todo_tasklet.
232+
todo: (fut, option) pair. fut is the future return by each add() call.
233+
234+
If the batch fut was successful, it has already called fut.set_result()
235+
on other individual futs. This method only handles when the batch fut
236+
encountered an exception.
237+
"""
238+
self._running.remove(batch_fut)
239+
err = batch_fut.get_exception()
177240
if err is not None:
178-
tb = fut.get_traceback()
179-
for (f, _) in todo:
180-
if not f.done():
181-
f.set_exception(err, tb)
241+
tb = batch_fut.get_traceback()
242+
for (fut, _) in todo:
243+
if not fut.done():
244+
fut.set_exception(err, tb)
182245

183246
@tasklets.tasklet
184247
def flush(self):
@@ -647,9 +710,8 @@ def get(self, key, **ctx_options):
647710
if use_cache:
648711
self._load_from_cache_if_available(key)
649712
if mvalue not in (_LOCKED, None):
650-
cls = model.Model._kind_map.get(key.kind())
651-
if cls is None:
652-
raise TypeError('Cannot find model class for kind %s' % key.kind())
713+
cls = model.Model._lookup_model(key.kind(),
714+
self._conn.adapter.default_model)
653715
pb = entity_pb.EntityProto()
654716

655717
try:

ndb/context_test.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,35 @@ def foo():
450450
})
451451
foo().check_success()
452452

453+
def testContext_MemcacheMissingKind(self):
454+
ctx = context.Context(
455+
conn=model.make_connection(default_model=None),
456+
auto_batcher_class=MyAutoBatcher)
457+
ctx.set_memcache_policy(False)
458+
ctx.set_cache_policy(False)
459+
460+
class Foo(model.Model):
461+
foo = model.IntegerProperty()
462+
bar = model.StringProperty()
463+
464+
key1 = model.Key(flat=('Foo', 1))
465+
ent1 = Foo(key=key1, foo=42, bar='hello')
466+
ctx.put(ent1).get_result()
467+
ctx.set_memcache_policy(True)
468+
ctx.get(key1).get_result() # Pull entity into memcache
469+
470+
model.Model._reset_kind_map()
471+
self.assertRaises(model.KindError, ctx.get(key1).get_result)
472+
473+
ctx = context.Context(
474+
conn=model.make_connection(default_model=Foo),
475+
auto_batcher_class=MyAutoBatcher)
476+
ctx.set_memcache_policy(True)
477+
ctx.set_cache_policy(False)
478+
479+
ent1_res = ctx.get(key1).get_result()
480+
self.assertEqual(ent1, ent1_res)
481+
453482
def testContext_MemcachePolicy(self):
454483
badkeys = []
455484
def tracking_add_async(*args, **kwds):

ndb/eventloop.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,25 @@ class EventLoop(object):
3737
"""An event loop."""
3838

3939
def __init__(self):
40-
"""Constructor."""
41-
self.current = collections.deque() # FIFO list of (callback, args, kwds)
42-
self.idlers = collections.deque() # Cyclic list of (callback, args, kwds)
40+
"""Constructor.
41+
42+
Fields:
43+
current: a FIFO list of (callback, args, kwds). These callbacks
44+
run immediately when the eventloop runs.
45+
idlers: a FIFO list of (callback, args, kwds). Thes callbacks
46+
run only when no other RPCs need to be fired first.
47+
For example, AutoBatcher uses idler to fire a batch RPC even before
48+
the batch is full.
49+
queue: a sorted list of (absolute time in sec, callback, args, kwds),
50+
sorted by time. These callbacks run only after the said time.
51+
rpcs: a map from rpc to (callback, args, kwds). Callback is called
52+
when the rpc finishes.
53+
"""
54+
self.current = collections.deque()
55+
self.idlers = collections.deque()
4356
self.inactive = 0 # How many idlers in a row were no-ops
44-
self.queue = [] # Sorted list of (time, callback, args, kwds)
45-
self.rpcs = {} # Map of rpc -> (callback, args, kwds)
57+
self.queue = []
58+
self.rpcs = {}
4659

4760
def clear(self):
4861
"""Remove all pending events without running any."""
@@ -75,6 +88,9 @@ def insort_event_right(self, event, lo=0, hi=None):
7588
7689
Optional args lo (default 0) and hi (default len(a)) bound the
7790
slice of a to be searched.
91+
92+
Args:
93+
event: a (time in sec since unix epoch, callback, args, kwds) tuple.
7894
"""
7995

8096
if lo < 0:

0 commit comments

Comments
 (0)