feat(routing): Add ABC for URI template field converters (#1084)
Also rename "fragment" to "value" to be consistent with the terminology used in the docstrings.
This commit is contained in:
parent
5c2d118fe9
commit
b70ae05c2d
|
@ -12,22 +12,42 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import abc
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
import six
|
||||
|
||||
|
||||
# PERF(kgriffs): Avoid an extra namespace lookup when using this function
|
||||
strptime = datetime.strptime
|
||||
|
||||
|
||||
class IntConverter(object):
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseConverter(object):
|
||||
"""Abstract base class for URI template field converters."""
|
||||
|
||||
@abc.abstractmethod # pragma: no cover
|
||||
def convert(self, value):
|
||||
"""Convert a URI template field value to another format or type.
|
||||
|
||||
Args:
|
||||
value (str): Original string to convert.
|
||||
|
||||
Returns:
|
||||
object: Converted field value, or ``None`` if the field
|
||||
can not be converted.
|
||||
"""
|
||||
|
||||
|
||||
class IntConverter(BaseConverter):
|
||||
"""Converts a field value to an int.
|
||||
|
||||
Keyword Args:
|
||||
num_digits (int): Require the value to have the given
|
||||
number of digits.
|
||||
min (int): Reject the value if it is less than this value.
|
||||
max (int): Reject the value if it is greater than this value.
|
||||
min (int): Reject the value if it is less than this number.
|
||||
max (int): Reject the value if it is greater than this number.
|
||||
"""
|
||||
|
||||
__slots__ = ('_num_digits', '_min', '_max')
|
||||
|
@ -40,20 +60,20 @@ class IntConverter(object):
|
|||
self._min = min
|
||||
self._max = max
|
||||
|
||||
def convert(self, fragment):
|
||||
if self._num_digits is not None and len(fragment) != self._num_digits:
|
||||
def convert(self, value):
|
||||
if self._num_digits is not None and len(value) != self._num_digits:
|
||||
return None
|
||||
|
||||
# NOTE(kgriffs): int() will accept numbers with preceding or
|
||||
# trailing whitespace, so we need to do our own check. Using
|
||||
# strip() is faster than either a regex or a series of or'd
|
||||
# membership checks via "in", esp. as the length of contiguous
|
||||
# numbers in the fragment grows.
|
||||
if fragment.strip() != fragment:
|
||||
# numbers in the value grows.
|
||||
if value.strip() != value:
|
||||
return None
|
||||
|
||||
try:
|
||||
value = int(fragment)
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
@ -66,7 +86,7 @@ class IntConverter(object):
|
|||
return value
|
||||
|
||||
|
||||
class DateTimeConverter(object):
|
||||
class DateTimeConverter(BaseConverter):
|
||||
"""Converts a field value to a datetime.
|
||||
|
||||
Keyword Args:
|
||||
|
@ -80,14 +100,14 @@ class DateTimeConverter(object):
|
|||
def __init__(self, format_string='%Y-%m-%dT%H:%M:%SZ'):
|
||||
self._format_string = format_string
|
||||
|
||||
def convert(self, fragment):
|
||||
def convert(self, value):
|
||||
try:
|
||||
return strptime(fragment, self._format_string)
|
||||
return strptime(value, self._format_string)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
class UUIDConverter(object):
|
||||
class UUIDConverter(BaseConverter):
|
||||
"""Converts a field value to a uuid.UUID.
|
||||
|
||||
In order to be converted, the field value must consist of a
|
||||
|
@ -95,9 +115,9 @@ class UUIDConverter(object):
|
|||
Note, however, that hyphens and the URN prefix are optional.
|
||||
"""
|
||||
|
||||
def convert(self, fragment):
|
||||
def convert(self, value):
|
||||
try:
|
||||
return uuid.UUID(fragment)
|
||||
return uuid.UUID(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ _TEST_UUID_STR = str(_TEST_UUID)
|
|||
_TEST_UUID_STR_SANS_HYPHENS = _TEST_UUID_STR.replace('-', '')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('fragment, num_digits, min, max, expected', [
|
||||
@pytest.mark.parametrize('value, num_digits, min, max, expected', [
|
||||
('123', None, None, None, 123),
|
||||
('01', None, None, None, 1),
|
||||
('001', None, None, None, 1),
|
||||
|
@ -40,19 +40,19 @@ _TEST_UUID_STR_SANS_HYPHENS = _TEST_UUID_STR.replace('-', '')
|
|||
('12', 2, 13, 12, None),
|
||||
('12', 2, 13, 13, None),
|
||||
])
|
||||
def test_int_converter(fragment, num_digits, min, max, expected):
|
||||
def test_int_converter(value, num_digits, min, max, expected):
|
||||
c = converters.IntConverter(num_digits, min, max)
|
||||
assert c.convert(fragment) == expected
|
||||
assert c.convert(value) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('fragment', (
|
||||
@pytest.mark.parametrize('value', (
|
||||
['0x0F', 'something', '', ' '] +
|
||||
['123' + w for w in string.whitespace] +
|
||||
[w + '123' for w in string.whitespace]
|
||||
))
|
||||
def test_int_converter_malformed(fragment):
|
||||
def test_int_converter_malformed(value):
|
||||
c = converters.IntConverter()
|
||||
assert c.convert(fragment) is None
|
||||
assert c.convert(value) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('num_digits', [0, -1, -10])
|
||||
|
@ -61,7 +61,7 @@ def test_int_converter_invalid_config(num_digits):
|
|||
converters.IntConverter(num_digits)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('fragment, format_string, expected', [
|
||||
@pytest.mark.parametrize('value, format_string, expected', [
|
||||
('07-03-17', '%m-%d-%y', datetime(2017, 7, 3)),
|
||||
('07-03-17 ', '%m-%d-%y ', datetime(2017, 7, 3)),
|
||||
('2017-07-03T14:30:01Z', '%Y-%m-%dT%H:%M:%SZ', datetime(2017, 7, 3, 14, 30, 1)),
|
||||
|
@ -73,9 +73,9 @@ def test_int_converter_invalid_config(num_digits):
|
|||
(' 07-03-17', '%m-%d-%y', None),
|
||||
('07 -03-17', '%m-%d-%y', None),
|
||||
])
|
||||
def test_datetime_converter(fragment, format_string, expected):
|
||||
def test_datetime_converter(value, format_string, expected):
|
||||
c = converters.DateTimeConverter(format_string)
|
||||
assert c.convert(fragment) == expected
|
||||
assert c.convert(value) == expected
|
||||
|
||||
|
||||
def test_datetime_converter_default_format():
|
||||
|
@ -83,7 +83,7 @@ def test_datetime_converter_default_format():
|
|||
assert c.convert('2017-07-03T14:30:01Z') == datetime(2017, 7, 3, 14, 30, 1)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('fragment, expected', [
|
||||
@pytest.mark.parametrize('value, expected', [
|
||||
(_TEST_UUID_STR, _TEST_UUID),
|
||||
(_TEST_UUID_STR.replace('-', '', 1), _TEST_UUID),
|
||||
(_TEST_UUID_STR_SANS_HYPHENS, _TEST_UUID),
|
||||
|
@ -98,6 +98,6 @@ def test_datetime_converter_default_format():
|
|||
(_TEST_UUID_STR[:-1] + 'g', None),
|
||||
(_TEST_UUID_STR.replace('-', '_'), None),
|
||||
])
|
||||
def test_uuid_converter(fragment, expected):
|
||||
def test_uuid_converter(value, expected):
|
||||
c = converters.UUIDConverter()
|
||||
assert c.convert(fragment) == expected
|
||||
assert c.convert(value) == expected
|
||||
|
|
Loading…
Reference in New Issue