Skip to content

Commit 0d4e19b

Browse files
author
Chris Rossi
authored
fix: correct regression in Model.get_or_insert (#731)
`Model.get_or_insert` can now handle and disambiguate arguments for model properties with the same name as other options, such as `name`, `cls`, `parent`, `timeout`, etc... This restores behavior of legacy NDB version. Fixes #729
1 parent 07fcf4e commit 0d4e19b

3 files changed

Lines changed: 165 additions & 80 deletions

File tree

packages/google-cloud-ndb/google/cloud/ndb/_options.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ class Options(object):
4242
)
4343

4444
@classmethod
45-
def options(cls, wrapped):
45+
def options_or_model_properties(cls, wrapped):
46+
return cls.options(wrapped, _disambiguate_from_model_properties=True)
47+
48+
@classmethod
49+
def options(cls, wrapped, _disambiguate_from_model_properties=False):
4650
slots = set(cls.slots())
4751
# If there are any positional arguments, get their names.
4852
# inspect.signature is not available in Python 2.7, so we use the
@@ -76,10 +80,19 @@ def wrapper(*args, **kwargs):
7680
else:
7781
pass_args.append(value)
7882

83+
if _disambiguate_from_model_properties:
84+
model_class = args[0]
85+
get_arg = model_class._get_arg
86+
87+
else:
88+
89+
def get_arg(kwargs, name):
90+
return kwargs.pop(name, None)
91+
7992
# Process keyword args
8093
for name in slots:
8194
if name not in kw_options:
82-
kw_options[name] = kwargs.pop(name, None)
95+
kw_options[name] = get_arg(kwargs, name)
8396

8497
# If another function that uses options is delegating to this one,
8598
# we'll already have options.

packages/google-cloud-ndb/google/cloud/ndb/model.py

Lines changed: 29 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ class Person(Model):
269269
from google.cloud.ndb import _datastore_types
270270
from google.cloud.ndb import exceptions
271271
from google.cloud.ndb import key as key_module
272-
from google.cloud.ndb import _options
272+
from google.cloud.ndb import _options as options_module
273273
from google.cloud.ndb import query as query_module
274274
from google.cloud.ndb import _transaction
275275
from google.cloud.ndb import tasklets
@@ -5332,7 +5332,7 @@ def _gql(cls, query_string, *args, **kwargs):
53325332

53335333
gql = _gql
53345334

5335-
@_options.Options.options
5335+
@options_module.Options.options
53365336
@utils.keyword_only(
53375337
retries=None,
53385338
timeout=None,
@@ -5383,7 +5383,7 @@ def _put(self, **kwargs):
53835383

53845384
put = _put
53855385

5386-
@_options.Options.options
5386+
@options_module.Options.options
53875387
@utils.keyword_only(
53885388
retries=None,
53895389
timeout=None,
@@ -5538,7 +5538,7 @@ def _query(cls, *filters, **kwargs):
55385538
query = _query
55395539

55405540
@classmethod
5541-
@_options.Options.options
5541+
@options_module.Options.options
55425542
@utils.positional(4)
55435543
def _allocate_ids(
55445544
cls,
@@ -5595,7 +5595,7 @@ def _allocate_ids(
55955595
allocate_ids = _allocate_ids
55965596

55975597
@classmethod
5598-
@_options.Options.options
5598+
@options_module.Options.options
55995599
@utils.positional(4)
56005600
def _allocate_ids_async(
56015601
cls,
@@ -5683,7 +5683,7 @@ def allocate_ids():
56835683
allocate_ids_async = _allocate_ids_async
56845684

56855685
@classmethod
5686-
@_options.ReadOptions.options
5686+
@options_module.ReadOptions.options
56875687
@utils.positional(6)
56885688
def _get_by_id(
56895689
cls,
@@ -5766,7 +5766,7 @@ def _get_by_id(
57665766
get_by_id = _get_by_id
57675767

57685768
@classmethod
5769-
@_options.ReadOptions.options
5769+
@options_module.ReadOptions.options
57705770
@utils.positional(6)
57715771
def _get_by_id_async(
57725772
cls,
@@ -5860,32 +5860,9 @@ def _get_by_id_async(
58605860
get_by_id_async = _get_by_id_async
58615861

58625862
@classmethod
5863-
@_options.ReadOptions.options
5863+
@options_module.ReadOptions.options_or_model_properties
58645864
@utils.positional(6)
5865-
def _get_or_insert(
5866-
cls,
5867-
name,
5868-
parent=None,
5869-
namespace=None,
5870-
project=None,
5871-
app=None,
5872-
read_consistency=None,
5873-
read_policy=None,
5874-
transaction=None,
5875-
retries=None,
5876-
timeout=None,
5877-
deadline=None,
5878-
use_cache=None,
5879-
use_global_cache=None,
5880-
global_cache_timeout=None,
5881-
use_datastore=None,
5882-
use_memcache=None,
5883-
memcache_timeout=None,
5884-
max_memcache_items=None,
5885-
force_writes=None,
5886-
_options=None,
5887-
**kw_model_args
5888-
):
5865+
def _get_or_insert(_cls, _name, *args, **kwargs):
58895866
"""Transactionally retrieves an existing entity or creates a new one.
58905867
58915868
Will attempt to look up an entity with the given ``name`` and
@@ -5943,45 +5920,14 @@ def _get_or_insert(
59435920
Returns:
59445921
Model: The entity that was either just retrieved or created.
59455922
"""
5946-
return cls._get_or_insert_async(
5947-
name,
5948-
parent=parent,
5949-
namespace=namespace,
5950-
project=project,
5951-
app=app,
5952-
_options=_options,
5953-
**kw_model_args
5954-
).result()
5923+
return _cls._get_or_insert_async(_name, *args, **kwargs).result()
59555924

59565925
get_or_insert = _get_or_insert
59575926

59585927
@classmethod
5959-
@_options.ReadOptions.options
5928+
@options_module.ReadOptions.options_or_model_properties
59605929
@utils.positional(6)
5961-
def _get_or_insert_async(
5962-
cls,
5963-
name,
5964-
parent=None,
5965-
namespace=None,
5966-
project=None,
5967-
app=None,
5968-
read_consistency=None,
5969-
read_policy=None,
5970-
transaction=None,
5971-
retries=None,
5972-
timeout=None,
5973-
deadline=None,
5974-
use_cache=None,
5975-
use_global_cache=None,
5976-
global_cache_timeout=None,
5977-
use_datastore=None,
5978-
use_memcache=None,
5979-
memcache_timeout=None,
5980-
max_memcache_items=None,
5981-
force_writes=None,
5982-
_options=None,
5983-
**kw_model_args
5984-
):
5930+
def _get_or_insert_async(_cls, _name, *args, **kwargs):
59855931
"""Transactionally retrieves an existing entity or creates a new one.
59865932
59875933
This is the asynchronous version of :meth:``_get_or_insert``.
@@ -6034,6 +5980,13 @@ def _get_or_insert_async(
60345980
tasklets.Future: Model: The entity that was either just retrieved
60355981
or created.
60365982
"""
5983+
name = _name
5984+
parent = _cls._get_arg(kwargs, "parent")
5985+
namespace = _cls._get_arg(kwargs, "namespace")
5986+
app = _cls._get_arg(kwargs, "app")
5987+
project = _cls._get_arg(kwargs, "project")
5988+
options = kwargs.pop("_options")
5989+
60375990
if not isinstance(name, six.string_types):
60385991
raise TypeError("'name' must be a string; received {!r}".format(name))
60395992

@@ -6056,21 +6009,21 @@ def _get_or_insert_async(
60566009
if namespace is not None:
60576010
key_args["namespace"] = namespace
60586011

6059-
key = key_module.Key(cls._get_kind(), name, parent=parent, **key_args)
6012+
key = key_module.Key(_cls._get_kind(), name, parent=parent, **key_args)
60606013

60616014
@tasklets.tasklet
60626015
def get_or_insert():
60636016
@tasklets.tasklet
60646017
def insert():
6065-
entity = cls(**kw_model_args)
6018+
entity = _cls(**kwargs)
60666019
entity._key = key
6067-
yield entity.put_async(_options=_options)
6020+
yield entity.put_async(_options=options)
60686021

60696022
raise tasklets.Return(entity)
60706023

60716024
# We don't need to start a transaction just to check if the entity
60726025
# exists already
6073-
entity = yield key.get_async(_options=_options)
6026+
entity = yield key.get_async(_options=options)
60746027
if entity is not None:
60756028
raise tasklets.Return(entity)
60766029

@@ -6303,7 +6256,7 @@ def __delattr__(self, name):
63036256
del self._properties[name]
63046257

63056258

6306-
@_options.ReadOptions.options
6259+
@options_module.ReadOptions.options
63076260
@utils.positional(1)
63086261
def get_multi_async(
63096262
keys,
@@ -6364,7 +6317,7 @@ def get_multi_async(
63646317
return [key.get_async(_options=_options) for key in keys]
63656318

63666319

6367-
@_options.ReadOptions.options
6320+
@options_module.ReadOptions.options
63686321
@utils.positional(1)
63696322
def get_multi(
63706323
keys,
@@ -6427,7 +6380,7 @@ def get_multi(
64276380
return [future.result() for future in futures]
64286381

64296382

6430-
@_options.Options.options
6383+
@options_module.Options.options
64316384
@utils.positional(1)
64326385
def put_multi_async(
64336386
entities,
@@ -6476,7 +6429,7 @@ def put_multi_async(
64766429
return [entity.put_async(_options=_options) for entity in entities]
64776430

64786431

6479-
@_options.Options.options
6432+
@options_module.Options.options
64806433
@utils.positional(1)
64816434
def put_multi(
64826435
entities,
@@ -6526,7 +6479,7 @@ def put_multi(
65266479
return [future.result() for future in futures]
65276480

65286481

6529-
@_options.Options.options
6482+
@options_module.Options.options
65306483
@utils.positional(1)
65316484
def delete_multi_async(
65326485
keys,
@@ -6575,7 +6528,7 @@ def delete_multi_async(
65756528
return [key.delete_async(_options=_options) for key in keys]
65766529

65776530

6578-
@_options.Options.options
6531+
@options_module.Options.options
65796532
@utils.positional(1)
65806533
def delete_multi(
65816534
keys,

0 commit comments

Comments
 (0)