From b787243c92ea2015b3f4cf2f285e697f56456459 Mon Sep 17 00:00:00 2001 From: Abhinav Desor Date: Wed, 3 May 2017 12:48:44 +0530 Subject: [PATCH 1/2] Refactor isinstance checks in validations.py --- filters/validations.py | 43 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/filters/validations.py b/filters/validations.py index 2181b25..9c5723b 100644 --- a/filters/validations.py +++ b/filters/validations.py @@ -1,9 +1,14 @@ # This module is define and keep all generic type of data-validations. -import six +import sys +import numbers import re from voluptuous import Invalid from django.utils.dateparse import parse_datetime, parse_date +# Forward compatibility with Python 3.x +if sys.version_info.major == 3: + basestring = str + def IntegerLike(msg=None): ''' @@ -14,13 +19,11 @@ def IntegerLike(msg=None): - str or unicode composed only of digits ''' def fn(value): - if not ( - isinstance(value, int) or - (isinstance(value, float) and value.is_integer()) or - (isinstance(value, str) and value.isdigit()) or - ((isinstance(value, unicode) and value.isdigit() or - isinstance(value, long)) if six.PY2 else None) - ): + if not any([ + isinstance(value, numbers.Integral), + (isinstance(value, float) and value.is_integer()), + (isinstance(value, basestring) and value.isdigit()) + ]): raise Invalid(msg or ( 'Invalid input <{0}>; expected an integer'.format(value)) ) @@ -38,13 +41,11 @@ def Alphanumeric(msg=None): - str or unicode composed only of alphanumeric characters ''' def fn(value): - if not ( - isinstance(value, int) or - (isinstance(value, float) and value.is_integer()) or - (isinstance(value, str) and value.isalnum()) or - ((isinstance(value, unicode) and value.isdigit() or - isinstance(value, long)) if six.PY2 else None) - ): + if not any([ + isinstance(value, numbers.Integral), + (isinstance(value, float) and value.is_integer()), + (isinstance(value, basestring) and value.isalnum()) + ]): raise Invalid(msg or ( 'Invalid input <{0}>; expected an integer'.format(value)) ) @@ -64,11 +65,11 @@ def StrictlyAlphanumeric(msg=None): - composed of both alphabets and digits ''' def fn(value): - if not ( - (isinstance(value, str) or (isinstance(value, unicode) if six.PY2 else None)) and - re_alphabets.search(value) and + if not all([ + isinstance(value, basestring), + re_alphabets.search(value), re_digits.search(value) - ): + ]): raise Invalid(msg or ( 'Invalid input <{0}>; expected an integer'.format(value)) ) @@ -97,12 +98,12 @@ def fn(value): def CSVofIntegers(msg=None): ''' Checks whether a value is list of integers. - Returns list of integers or just one integer in + Returns list of integers or just one integer in list if there is only one element in given CSV string. ''' def fn(value): try: - if isinstance(value, six.text_type): + if isinstance(value, basestring): if ',' in value: value = list(map( int, filter( From 84dfd9bda8413a9efbd7b021c8f9d705cfe74db0 Mon Sep 17 00:00:00 2001 From: Abhinav Desor Date: Wed, 3 May 2017 17:09:13 +0530 Subject: [PATCH 2/2] Add tests --- .travis.yml | 6 -- filters/tests/__init__.py | 3 + {tests => filters/tests}/test_mixins.py | 0 filters/tests/test_validations.py | 77 +++++++++++++++++++++++++ {tests => filters/tests}/tests.py | 0 filters/validations.py | 18 +++--- setup.py | 10 ++-- tests/__init__.py | 0 8 files changed, 94 insertions(+), 20 deletions(-) delete mode 100644 .travis.yml create mode 100644 filters/tests/__init__.py rename {tests => filters/tests}/test_mixins.py (100%) create mode 100644 filters/tests/test_validations.py rename {tests => filters/tests}/tests.py (100%) delete mode 100644 tests/__init__.py diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9eb2aba..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: python -sudo: false -python: - - 2.7 -script: - python -m unittest tests.tests diff --git a/filters/tests/__init__.py b/filters/tests/__init__.py new file mode 100644 index 0000000..5c55ccd --- /dev/null +++ b/filters/tests/__init__.py @@ -0,0 +1,3 @@ +from django.conf import settings + +settings.configure() diff --git a/tests/test_mixins.py b/filters/tests/test_mixins.py similarity index 100% rename from tests/test_mixins.py rename to filters/tests/test_mixins.py diff --git a/filters/tests/test_validations.py b/filters/tests/test_validations.py new file mode 100644 index 0000000..3fd52e9 --- /dev/null +++ b/filters/tests/test_validations.py @@ -0,0 +1,77 @@ +import unittest +from nose2.tools.such import helper +from voluptuous import Invalid +from filters import validations + +INT = 1 +LONG = long(INT) +INT_FLOAT = float(INT) +INT_STR = str(INT) +INT_UNICODE = unicode(INT) +ALNUM_STR = "hello123" +ALNUM_UNICODE = unicode(ALNUM_STR) +NON_INT_FLOAT = 1.5 +NON_INT_STR = str(NON_INT_FLOAT) +NON_INT_UNICODE = unicode(NON_INT_FLOAT) +NON_ALNUM_STR = "hello 123" +NON_ALNUM_UNICODE = unicode(NON_ALNUM_STR) +INT_CSV = "1,2,3" +NON_INT_CSV = "a,b,c" + + +class BaseValidationTestCase(object): + def f(self, v): + return self.base_function()(v) + + def transform_val(self, v): + return v + + def test_valid_values(self): + for v in self.valid_values: + self.assertEqual(self.f(v), self.transform_val(v)) + + def test_invalid_values(self): + for v in self.invalid_values: + self.assertRaises(Invalid, self.f, v) + + +class IntegerLikeTestCase(BaseValidationTestCase, unittest.TestCase): + base_function = validations.IntegerLike + valid_values = [ + INT, LONG, INT_FLOAT, INT_STR, INT_UNICODE + ] + invalid_values = [ + NON_INT_FLOAT, NON_INT_STR, ALNUM_STR, ALNUM_UNICODE, + NON_INT_UNICODE, NON_ALNUM_STR, NON_ALNUM_UNICODE + ] + + +class AlphanumericTestCase(BaseValidationTestCase, unittest.TestCase): + base_function = validations.Alphanumeric + valid_values = [ + INT, LONG, INT_FLOAT, INT_STR, INT_UNICODE, ALNUM_STR, ALNUM_UNICODE + ] + invalid_values = [ + NON_INT_FLOAT, NON_INT_STR, NON_INT_UNICODE, NON_ALNUM_STR, + NON_ALNUM_UNICODE + ] + + +class StrictlyAlphanumericTestCase(BaseValidationTestCase, unittest.TestCase): + base_function = validations.StrictlyAlphanumeric + valid_values = [ALNUM_STR, ALNUM_UNICODE] + invalid_values = [ + INT, LONG, INT_FLOAT, NON_INT_FLOAT, NON_INT_STR, NON_INT_UNICODE, + NON_ALNUM_STR, NON_ALNUM_UNICODE, INT_STR, INT_UNICODE + ] + + +class CSVofIntegersTestCase(BaseValidationTestCase, unittest.TestCase): + base_function = validations.CSVofIntegers + transform_val = lambda self, v: map(int, v.split(",")) + valid_values = [INT_CSV, INT_STR, INT_UNICODE] + invalid_values = [ + INT_FLOAT, ALNUM_STR, ALNUM_UNICODE, NON_INT_FLOAT, NON_INT_STR, + NON_INT_UNICODE, NON_ALNUM_STR, NON_ALNUM_UNICODE, NON_INT_CSV, + INT, LONG + ] diff --git a/tests/tests.py b/filters/tests/tests.py similarity index 100% rename from tests/tests.py rename to filters/tests/tests.py diff --git a/filters/validations.py b/filters/validations.py index 9c5723b..8ac9f55 100644 --- a/filters/validations.py +++ b/filters/validations.py @@ -1,7 +1,6 @@ # This module is define and keep all generic type of data-validations. import sys import numbers -import re from voluptuous import Invalid from django.utils.dateparse import parse_datetime, parse_date @@ -54,10 +53,6 @@ def fn(value): return fn -re_alphabets = re.compile('[A-Za-z]') -re_digits = re.compile('[0-9]') - - def StrictlyAlphanumeric(msg=None): ''' Checks whether a value is: @@ -65,11 +60,12 @@ def StrictlyAlphanumeric(msg=None): - composed of both alphabets and digits ''' def fn(value): - if not all([ - isinstance(value, basestring), - re_alphabets.search(value), - re_digits.search(value) - ]): + if not ( + isinstance(value, basestring) and + value.isalnum() and not + value.isdigit() and not + value.isalpha() + ): raise Invalid(msg or ( 'Invalid input <{0}>; expected an integer'.format(value)) ) @@ -115,6 +111,8 @@ def fn(value): return value else: return [int(value)] + else: + raise ValueError except ValueError: raise Invalid( '<{0}> is not a valid csv of integers'.format(value) diff --git a/setup.py b/setup.py index e84bf47..2f9fcb8 100644 --- a/setup.py +++ b/setup.py @@ -32,9 +32,9 @@ packages=['filters'], include_package_data=True, description=( - 'A django app to apply filters on drf querysets ' - 'using query params with validations using voluptuous.' - ), + 'A django app to apply filters on drf querysets ' + 'using query params with validations using voluptuous.' + ), long_description=README, url=__url__, download_url=__download_url__, @@ -51,5 +51,7 @@ 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 2.7', ], - keywords='drf-url-filters, filters, queryparamters', + keywords='drf-url-filters, filters, queryparameters', + test_suite='nose2.collector.collector', + tests_require=['nose2'], ) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000