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 2181b25..8ac9f55 100644 --- a/filters/validations.py +++ b/filters/validations.py @@ -1,9 +1,13 @@ # This module is define and keep all generic type of data-validations. -import six -import re +import sys +import numbers 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 +18,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 +40,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)) ) @@ -53,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,9 +61,10 @@ def StrictlyAlphanumeric(msg=None): ''' def fn(value): if not ( - (isinstance(value, str) or (isinstance(value, unicode) if six.PY2 else None)) and - re_alphabets.search(value) and - re_digits.search(value) + 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)) @@ -97,12 +94,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( @@ -114,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