Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merging v.1.3.8 into master in preparation for release. #48

Merged
merged 10 commits into from
Dec 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ venv/
.clean
ENV/
.activate.ps1
.pyinstaller27/
.pyinstaller36/

# Spyder project settings
.spyderproject
Expand Down
8 changes: 8 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
-----------

Release 1.3.8 (released December 7, 2019)
============================================

* #43: Modified how ``__init__.py`` reads version information.
* #45: Fixed false-positives and false-negatives in iterable validation.

-----------

Release 1.3.7 (released September 7, 2019)
============================================

Expand Down
10 changes: 5 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ Validator Collection
:target: http://validator-collection.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status (ReadTheDocs)

* - `v. 1.3 <https://github.com/insightindustry/validator-collection/tree/v.1.3.7>`_
* - `v. 1.3 <https://github.com/insightindustry/validator-collection/tree/v.1.3.8>`_
-
.. image:: https://travis-ci.org/insightindustry/validator-collection.svg?branch=v.1.3.7
.. image:: https://travis-ci.org/insightindustry/validator-collection.svg?branch=v.1.3.8
:target: https://travis-ci.org/insightindustry/validator-collection
:alt: Build Status (Travis CI)

.. image:: https://codecov.io/gh/insightindustry/validator-collection/branch/v.1.3.7/graph/badge.svg
.. image:: https://codecov.io/gh/insightindustry/validator-collection/branch/v.1.3.8/graph/badge.svg
:target: https://codecov.io/gh/insightindustry/validator-collection
:alt: Code Coverage Status (Codecov)

.. image:: https://readthedocs.org/projects/validator-collection/badge/?version=v.1.3.7
:target: http://validator-collection.readthedocs.io/en/latest/?badge=v.1.3.7
.. image:: https://readthedocs.org/projects/validator-collection/badge/?version=v.1.3.8
:target: http://validator-collection.readthedocs.io/en/latest/?badge=v.1.3.8
:alt: Documentation Status (ReadTheDocs)

* - `v. 1.2 <https://github.com/insightindustry/validator-collection/tree/v.1.2.0>`_
Expand Down
2 changes: 2 additions & 0 deletions docs/_contributors.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
* Chris Modzelewski (`@insightindustry <https://github.com/insightindustry/>`_)
* Jordan Reiter (`@JordanReiter <https://github.com/JordanReiter/>`_)
* Ihor Liubymov (`@mastak <https://github.com/mastak/>`_)
* Alon Bar Tzlil (`@aivrit <https://github.com/aivrit/>`_)
10 changes: 5 additions & 5 deletions docs/_unit_tests_code_coverage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@
:target: http://validator-collection.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status (ReadTheDocs)

* - `v. 1.3 <https://github.com/insightindustry/validator-collection/tree/v.1.3.7>`_
* - `v. 1.3 <https://github.com/insightindustry/validator-collection/tree/v.1.3.8>`_
-
.. image:: https://travis-ci.org/insightindustry/validator-collection.svg?branch=v.1.3.7
.. image:: https://travis-ci.org/insightindustry/validator-collection.svg?branch=v.1.3.8
:target: https://travis-ci.org/insightindustry/validator-collection
:alt: Build Status (Travis CI)

.. image:: https://codecov.io/gh/insightindustry/validator-collection/branch/v.1.3.7/graph/badge.svg
.. image:: https://codecov.io/gh/insightindustry/validator-collection/branch/v.1.3.8/graph/badge.svg
:target: https://codecov.io/gh/insightindustry/validator-collection
:alt: Code Coverage Status (Codecov)

.. image:: https://readthedocs.org/projects/validator-collection/badge/?version=v.1.3.7
:target: http://validator-collection.readthedocs.io/en/latest/?badge=v.1.3.7
.. image:: https://readthedocs.org/projects/validator-collection/badge/?version=v.1.3.8
:target: http://validator-collection.readthedocs.io/en/latest/?badge=v.1.3.8
:alt: Documentation Status (ReadTheDocs)

* - `v. 1.2 <https://github.com/insightindustry/validator-collection/tree/v.1.2.0>`_
Expand Down
5 changes: 5 additions & 0 deletions docs/errors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ NotAnIterableError (from :class:`CannotCoerceError <validator_collection.errors.

.. autoclass:: NotAnIterableError

IterationFailedError (from :class:`NotAnIterableError <validator_collection.errors.NotAnIterableError>`)
---------------------------------------------------------------------------------------------------------

.. autoclass:: IterationFailedError

NotCallableError (from :class:`ValueError <python:ValueError>`)
------------------------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

# Get the version number from the VERSION file
version_dict = {}
with open('./validator_collection/_version.py') as version_file:
with open(path.join(here, 'validator_collection', '_version.py')) as version_file:
exec(version_file.read(), version_dict) # pylint: disable=W0122

version = version_dict.get('__version__')
Expand Down
49 changes: 49 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,16 @@
"""

import abc
import collections
import os

import pytest
from validator_collection._compat import is_py2

if is_py2:
Iterable = collections.Iterable
else:
Iterable = collections.abc.Iterable


def pytest_addoption(parser):
Expand Down Expand Up @@ -91,3 +98,45 @@ class MetaClassParentType(object):
class MetaClassType(MetaClassParentType):
"""The child meta class type."""
pass


class GetItemIterable(object):
"""A class that does not have a ``__iter__`` method, but does use
``__getitem__``."""

items = (1, 2, 3, 4)

def __getitem__(self, key):
return self.items[key]


class IterIterable(object):
"""A class that has an ``__iter__`` method."""

def __iter__(self):
return self

def __next__(self):
raise StopIteration()

def next(self):
return self.__next__()

class IterableIterable(Iterable):
"""A class that inherits from ``Iterable``."""

def __iter__(self):
return self

def __next__(self):
raise StopIteration()

def next(self):
return self.__next__()


class FalseIterable(Iterable):
"""A class that inherits from ``Iterable``."""

def __iter__(self):
raise AttributeError('this simulates an AttributeError')
18 changes: 17 additions & 1 deletion tests/test_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@

from validator_collection._compat import TimeZone

from tests.conftest import MetaClassParentType, MetaClassType
from tests.conftest import MetaClassParentType, MetaClassType, GetItemIterable, \
IterIterable, IterableIterable, FalseIterable


## CORE
Expand All @@ -52,6 +53,12 @@

([1, 2, 3, 4], {'allow_empty': True}, SyntaxError),

(GetItemIterable(), None, True),
(IterIterable(), None, True),
(IterableIterable(), None, True),

(FalseIterable(), None, True),

])
def test_is_iterable(value, kwargs, expects):
if kwargs and isinstance(expects, bool):
Expand Down Expand Up @@ -278,9 +285,18 @@ def test_is_string(value, expects, coerce_value, minimum_length, maximum_length,
(['test', 123], True, False, None, 1),

(str, True, False, None, None),

(GetItemIterable(), False, False, None, None),
(IterIterable(), False, False, None, None),
(IterableIterable(), False, False, None, None),

(FalseIterable(), True, False, None, None),

])
def test_is_iterable(value, fails, allow_empty, minimum_length, maximum_length):
expects = not fails
if not fails:
iter(value)
result = checkers.is_iterable(value,
minimum_length = minimum_length,
maximum_length = maximum_length)
Expand Down
9 changes: 8 additions & 1 deletion tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import validator_collection.validators as validators
from validator_collection._compat import numeric_types, basestring
from tests.conftest import GetItemIterable, IterIterable, IterableIterable, FalseIterable


## CORE
Expand Down Expand Up @@ -248,6 +249,12 @@ def test_uuid(value, fails, allow_empty):
(['test', 123], True, False, 3, None),
(['test', 123], True, False, None, 1),

(GetItemIterable(), False, False, None, None),
(IterIterable(), False, False, None, None),
(IterableIterable(), False, False, None, None),

(FalseIterable(), True, False, None, None),

])
def test_iterable(value, fails, allow_empty, minimum_length, maximum_length):
"""Test the string validator."""
Expand All @@ -258,7 +265,7 @@ def test_iterable(value, fails, allow_empty, minimum_length, maximum_length):
maximum_length = maximum_length)
if value is not None:
assert validated is not None
assert hasattr(validated, '__iter__')
iter(validated)
else:
assert validated is None
else:
Expand Down
9 changes: 1 addition & 8 deletions validator_collection/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,7 @@
"""

import os

# Get the version number from the _version.py file
version_dict = {}
with open(os.path.join(os.path.dirname(__file__), '_version.py')) as version_file:
exec(version_file.read(), version_dict) # pylint: disable=W0122

__version__ = version_dict.get('__version__')

from validator_collection._version import __version__

from validator_collection.validators import bytesIO, date, dict, decimal, \
directory_exists, datetime, email, float, fraction, file_exists, ip_address, \
Expand Down
2 changes: 1 addition & 1 deletion validator_collection/_version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# -*- coding: utf-8 -*-
__version__ = '1.3.7'
__version__ = '1.3.8'
10 changes: 10 additions & 0 deletions validator_collection/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ class NotAnIterableError(CannotCoerceError):
"""
pass

class IterationFailedError(NotAnIterableError):
"""Exception raised when a value conforms to one of Python's supported
iterable protocols, but iterating across the object produced an unexpected
Exception.

**INHERITS FROM:** :class:`TypeError <python:TypeError>` -> :class:`CannotCoerceError <validator_collection.errors.CannotCoerceError>` -> :class:`NotAnIterableError <validator_collection.errors.NotAnIterableError>`

"""
pass


class MaximumLengthError(ValueError):
"""Exception raised when a value exceeds a maximum allowed length.
Expand Down
23 changes: 22 additions & 1 deletion validator_collection/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,18 @@ def iterable(value,
**kwargs):
"""Validate that ``value`` is a valid iterable.

.. hint::

This validator checks to ensure that ``value`` supports iteration using
any of Python's three iteration protocols: the ``__getitem__`` protocol,
the ``__iter__`` / ``next()`` protocol, or the inheritance from Python's
`Iterable` abstract base class.

If ``value`` supports any of these three iteration protocols, it will be
validated. However, if iteration across ``value`` raises an unsupported
exception, this function will raise an
:exc:`IterationFailedError <validator_collection.errors.IterationFailedError>`

:param value: The value to validate.

:param allow_empty: If ``True``, returns :obj:`None <python:None>` if ``value``
Expand Down Expand Up @@ -286,6 +298,8 @@ def iterable(value,
:raises EmptyValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises NotAnIterableError: if ``value`` is not a valid iterable or
:obj:`None <python:None>`
:raises IterationFailedError: if ``value`` is a valid iterable, but iteration
fails for some unexpected exception
:raises MinimumLengthError: if ``minimum_length`` is supplied and the length of
``value`` is less than ``minimum_length`` and ``whitespace_padding`` is
``False``
Expand All @@ -300,9 +314,16 @@ def iterable(value,
minimum_length = integer(minimum_length, allow_empty = True, force_run = True) # pylint: disable=E1123
maximum_length = integer(maximum_length, allow_empty = True, force_run = True) # pylint: disable=E1123

if isinstance(value, forbid_literals) or not hasattr(value, '__iter__'):
if isinstance(value, forbid_literals):
raise errors.NotAnIterableError('value type (%s) not iterable' % type(value))

try:
iter(value)
except TypeError:
raise errors.NotAnIterableError('value type (%s) not iterable' % type(value))
except Exception as error:
raise errors.IterationFailedError('iterating across value raised an unexpected Exception: "%s"' % error)

if value and minimum_length is not None and len(value) < minimum_length:
raise errors.MinimumLengthError(
'value has fewer items than the minimum length %s' % minimum_length
Expand Down