Skip to content

Commit bcf836e

Browse files
committed
Implementing query.Parameter.
1 parent 2ae5be5 commit bcf836e

2 files changed

Lines changed: 120 additions & 4 deletions

File tree

ndb/src/google/cloud/ndb/query.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
"""High-level wrapper for datastore queries."""
1616

17+
from google.cloud.ndb import _exceptions
18+
1719

1820
__all__ = [
1921
"Cursor",
@@ -64,8 +66,66 @@ def __ne__(self, other):
6466

6567

6668
class Parameter(ParameterizedThing):
67-
def __init__(self, *args, **kwargs):
68-
raise NotImplementedError
69+
"""Represents a bound variable in a GQL query.
70+
71+
``Parameter(1)`` corresponds to a slot labeled ``:1`` in a GQL query.
72+
``Parameter('xyz')`` corresponds to a slot labeled ``:xyz``.
73+
74+
The value must be set (bound) separately by calling :meth:`set`.
75+
76+
Args:
77+
key (Union[str, int]): The parameter key.
78+
79+
Raises:
80+
TypeError: If the ``key`` is not a string or integer.
81+
"""
82+
83+
def __init__(self, key):
84+
if not isinstance(key, (int, str, bytes)):
85+
raise TypeError(
86+
"Parameter key must be an integer or string, not {}".format(
87+
key
88+
)
89+
)
90+
self._key = key
91+
92+
def __repr__(self):
93+
return "{}({!r})".format(self.__class__.__name__, self._key)
94+
95+
def __eq__(self, other):
96+
if not isinstance(other, Parameter):
97+
return NotImplemented
98+
99+
return self._key == other._key
100+
101+
@property
102+
def key(self):
103+
"""Retrieve the key."""
104+
return self._key
105+
106+
def resolve(self, bindings, used):
107+
"""Resolve the current parameter from the parameter bindings.
108+
109+
Args:
110+
bindings (dict): A mapping of parameter bindings.
111+
used (Dict[Union[str, int], bool]): A mapping of already used
112+
parameters. This will be modified if the current parameter
113+
is in ``bindings``.
114+
115+
Returns:
116+
Any: The bound value for the current parameter.
117+
118+
Raises:
119+
.BadArgumentError: If the current parameter is not in ``bindings``.
120+
"""
121+
key = self._key
122+
if key not in bindings:
123+
raise _exceptions.BadArgumentError(
124+
"Parameter :{} is not bound.".format(key)
125+
)
126+
value = bindings[key]
127+
used[key] = True
128+
return value
69129

70130

71131
class ParameterizedFunction(ParameterizedThing):

ndb/tests/unit/test_query.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import unittest.mock
16+
1517
import pytest
1618

19+
from google.cloud.ndb import _exceptions
1720
from google.cloud.ndb import query
1821
import tests.unit.utils
1922

@@ -57,8 +60,61 @@ def test___ne__():
5760
class TestParameter:
5861
@staticmethod
5962
def test_constructor():
60-
with pytest.raises(NotImplementedError):
61-
query.Parameter()
63+
for key in (88, b"abc", "def"):
64+
parameter = query.Parameter(key)
65+
assert parameter._key == key
66+
67+
@staticmethod
68+
def test_constructor_invalid():
69+
with pytest.raises(TypeError):
70+
query.Parameter(None)
71+
72+
@staticmethod
73+
def test___repr__():
74+
parameter = query.Parameter("ghi")
75+
assert repr(parameter) == "Parameter('ghi')"
76+
77+
@staticmethod
78+
def test___eq__():
79+
parameter1 = query.Parameter("yep")
80+
parameter2 = query.Parameter("nope")
81+
parameter3 = unittest.mock.sentinel.parameter
82+
assert parameter1 == parameter1
83+
assert not parameter1 == parameter2
84+
assert not parameter1 == parameter3
85+
86+
@staticmethod
87+
def test___ne__():
88+
parameter1 = query.Parameter("yep")
89+
parameter2 = query.Parameter("nope")
90+
parameter3 = unittest.mock.sentinel.parameter
91+
assert not parameter1 != parameter1
92+
assert parameter1 != parameter2
93+
assert parameter1 != parameter3
94+
95+
@staticmethod
96+
def test_key():
97+
parameter = query.Parameter(9000)
98+
assert parameter.key == 9000
99+
100+
@staticmethod
101+
def test_resolve():
102+
key = 9000
103+
bound_value = "resoolt"
104+
parameter = query.Parameter(key)
105+
used = {}
106+
result = parameter.resolve({key: bound_value}, used)
107+
assert result == bound_value
108+
assert used == {key: True}
109+
110+
@staticmethod
111+
def test_resolve_missing_key():
112+
parameter = query.Parameter(9000)
113+
used = {}
114+
with pytest.raises(_exceptions.BadArgumentError):
115+
parameter.resolve({}, used)
116+
117+
assert used == {}
62118

63119

64120
class TestParameterizedFunction:

0 commit comments

Comments
 (0)