Skip to content

Commit 9aa31dc

Browse files
authored
Merge pull request #200 from technige/1.5-coerce-params
Parameter type checking
2 parents 42729cb + 789b44f commit 9aa31dc

File tree

2 files changed

+106
-10
lines changed

2 files changed

+106
-10
lines changed

neo4j/v1/api.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,11 @@
2121

2222
from collections import deque
2323
from random import random
24-
from threading import RLock
2524
from time import time, sleep
2625
from warnings import warn
2726

2827
from neo4j.exceptions import ProtocolError, ServiceUnavailable
29-
from neo4j.compat import urlparse
28+
from neo4j.compat import urlparse, ustr, string, integer
3029
from neo4j.exceptions import CypherError, TransientError
3130
from neo4j.config import default_config
3231

@@ -41,6 +40,9 @@
4140
RETRY_DELAY_MULTIPLIER = 2.0
4241
RETRY_DELAY_JITTER_FACTOR = 0.2
4342

43+
INT64_MIN = -(2 ** 63)
44+
INT64_MAX = (2 ** 63) - 1
45+
4446

4547
def last_bookmark(b0, b1):
4648
""" Return the latest of two bookmarks by looking for the maximum
@@ -786,12 +788,30 @@ def fix_statement(statement):
786788
return statement
787789

788790

791+
def coerce_parameters(x):
792+
if x is None:
793+
return None
794+
elif isinstance(x, bool):
795+
return x
796+
elif isinstance(x, integer):
797+
if INT64_MIN <= x <= INT64_MAX:
798+
return x
799+
raise ValueError("Integer out of bounds (64-bit signed integer values only)")
800+
elif isinstance(x, float):
801+
return x
802+
elif isinstance(x, string):
803+
return ustr(x)
804+
elif isinstance(x, (bytes, bytearray)): # the order is important here - bytes must be checked after string
805+
return x
806+
elif isinstance(x, list):
807+
return list(map(coerce_parameters, x))
808+
elif isinstance(x, dict):
809+
return {ustr(key): coerce_parameters(value) for key, value in x.items()}
810+
else:
811+
raise TypeError("Parameters of type {} are not supported".format(type(x).__name__))
812+
813+
789814
def fix_parameters(parameters=None, **kwparameters):
790-
params_in = parameters or {}
791-
params_in.update(kwparameters)
792-
params_out = {}
793-
for key, value in params_in.items():
794-
if isinstance(key, bytes):
795-
key = key.decode("UTF-8")
796-
params_out[key] = value
797-
return params_out
815+
p = parameters or {}
816+
p.update(kwparameters)
817+
return coerce_parameters(p)

test/unit/test_api.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env python
2+
# -*- encoding: utf-8 -*-
3+
4+
# Copyright (c) 2002-2017 "Neo Technology,"
5+
# Network Engine for Objects in Lund AB [http://neotechnology.com]
6+
#
7+
# This file is part of Neo4j.
8+
#
9+
# Licensed under the Apache License, Version 2.0 (the "License");
10+
# you may not use this file except in compliance with the License.
11+
# You may obtain a copy of the License at
12+
#
13+
# http://www.apache.org/licenses/LICENSE-2.0
14+
#
15+
# Unless required by applicable law or agreed to in writing, software
16+
# distributed under the License is distributed on an "AS IS" BASIS,
17+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
# See the License for the specific language governing permissions and
19+
# limitations under the License.
20+
21+
22+
from unittest import TestCase
23+
from uuid import uuid4
24+
25+
from neo4j.v1.api import coerce_parameters
26+
27+
28+
class ParameterTypeTestCase(TestCase):
29+
def test_should_allow_none(self):
30+
self.assertIsNone(coerce_parameters(None))
31+
32+
def test_should_allow_boolean(self):
33+
self.assertTrue(coerce_parameters(True))
34+
self.assertFalse(coerce_parameters(False))
35+
36+
def test_should_allow_integer(self):
37+
self.assertEqual(coerce_parameters(0), 0)
38+
self.assertEqual(coerce_parameters(0x7F), 0x7F)
39+
self.assertEqual(coerce_parameters(0x7FFF), 0x7FFF)
40+
self.assertEqual(coerce_parameters(0x7FFFFFFF), 0x7FFFFFFF)
41+
self.assertEqual(coerce_parameters(0x7FFFFFFFFFFFFFFF), 0x7FFFFFFFFFFFFFFF)
42+
43+
def test_should_disallow_oversized_integer(self):
44+
with self.assertRaises(ValueError):
45+
coerce_parameters(0x10000000000000000)
46+
with self.assertRaises(ValueError):
47+
coerce_parameters(-0x10000000000000000)
48+
49+
def test_should_allow_float(self):
50+
self.assertEqual(coerce_parameters(0.0), 0.0)
51+
self.assertEqual(coerce_parameters(3.1415926), 3.1415926)
52+
53+
def test_should_allow_string(self):
54+
self.assertEqual(coerce_parameters(u""), u"")
55+
self.assertEqual(coerce_parameters(u"hello, world"), u"hello, world")
56+
57+
def test_should_allow_bytes(self):
58+
self.assertEqual(coerce_parameters(bytearray()), bytearray())
59+
self.assertEqual(coerce_parameters(bytearray([1, 2, 3])), bytearray([1, 2, 3]))
60+
61+
def test_should_allow_list(self):
62+
self.assertEqual(coerce_parameters([]), [])
63+
self.assertEqual(coerce_parameters([1, 2, 3]), [1, 2, 3])
64+
65+
def test_should_allow_dict(self):
66+
self.assertEqual(coerce_parameters({}), {})
67+
self.assertEqual(coerce_parameters({u"one": 1, u"two": 1, u"three": 1}), {u"one": 1, u"two": 1, u"three": 1})
68+
self.assertEqual(coerce_parameters(
69+
{u"list": [1, 2, 3, [4, 5, 6]], u"dict": {u"a": 1, u"b": 2}}),
70+
{u"list": [1, 2, 3, [4, 5, 6]], u"dict": {u"a": 1, u"b": 2}})
71+
72+
def test_should_disallow_object(self):
73+
with self.assertRaises(TypeError):
74+
coerce_parameters(object())
75+
with self.assertRaises(TypeError):
76+
coerce_parameters(uuid4())

0 commit comments

Comments
 (0)