Skip to content

Commit

Permalink
Merge pull request #65 from neverendingqs/0.4.0/refactorparams
Browse files Browse the repository at this point in the history
0.4.0/refactorparams; resolves #59
  • Loading branch information
neverendingqs committed Oct 4, 2015
2 parents 38361d1 + c6b6f09 commit 454f808
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 39 deletions.
76 changes: 44 additions & 32 deletions pyiterable/iterable.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from functools import reduce
import itertools
import warnings


class Iterable:
Expand Down Expand Up @@ -289,30 +290,30 @@ def mapmany(self, function):
"""
return Iterable(itertools.chain.from_iterable(map(function, self.__iterable)))

def single(self, function=None, default=None):
def single(self, filter_by=None, default=None):
""" Equivalent to calling **first()**, except it raises *ValueError* if *iterable* contains more than one element
:param function: keyword-only; function used to filter unwanted values
:param default: keyword-only value to return if *self* is empty after filtered by *function*
:return: value of *self* filtered by *function*
:param filter_by: keyword-only; function used to filter unwanted values
:param default: keyword-only value to return if *self* is empty after filtered by *filter_by*
:return: value of *self* filtered by *filter_by*
:raises ValueError: *iterable* contains more than one element after being filtered by *filter_by*
:raises ValueError: *iterable* contains more than one element after being filtered by *function*
>>> values = Iterable([1, 2, 5, 9])
>>> values.single()
ValueError: iterable [1, 2, 5, 9] contains more than one element
>>> values.single(function=lambda x: x > 1)
>>> values.single(filter_by=lambda x: x > 1)
ValueError: iterable [2, 5, 9] contains more than one element
>>> values.single(function=lambda x: x > 5)
>>> values.single(filter_by=lambda x: x > 5)
9
>>> values.single(function=lambda x: x > 10) # Returns None
>>> values.single(function=lambda x: x > 10, default=0)
>>> values.single(filter_by=lambda x: x > 10) # Returns None
>>> values.single(filter_by=lambda x: x > 10, default=0)
0
"""
if function is None:
if filter_by is None:
filtered_self = self
else:
filtered_self = self.filter(function)
filtered_self = self.filter(filter_by)

if filtered_self.len() > 1:
raise ValueError("iterable {} contains more than one element".format(filtered_self.__iterable))
Expand All @@ -334,24 +335,35 @@ def concat(self, iterable):
"""
return Iterable(list(self.__iterable) + list(iterable))

def first(self, function=None, default=None):
""" Equivalent to calling **next( iter( filter(** *function, iterable* **) )** *, default* **)**
def first(self, filter_by=None, default=None, function=None):
""" Equivalent to calling **next( iter( filter(** *filter_by, iterable* **) )** *, default* **)**
:param function: keyword-only; function used to filter unwanted values
:param default: keyword-only value to return if *self* is empty after filtered by *function*
:return: first value of *self* filtered by *function*
:param filter_by: keyword-only; function used to filter unwanted values
:param default: keyword-only; value to return if *self* is empty after filtered by *filter_by*
:param function: deprecated; use *filter_by*
:return: first value of *self* filtered by *filter_by*
>>> values = Iterable([1, 2, 5, 9])
>>> values.first()
1
>>> values.first(function=lambda x: x > 5)
>>> values.first(filter_by=lambda x: x > 5)
9
>>> values.first(function=lambda x: x > 10) # Returns None
>>> values.first(function=lambda x: x > 10, default=0)
>>> values.first(filter_by=lambda x: x > 10) # Returns None
>>> values.first(filter_by=lambda x: x > 10, default=0)
0
"""
if function:
return next(iter(filter(function, self.__iterable)), default)
if function is not None:
warnings.warn(
"'function' is deprecated; use 'filter_by' instead",
category=DeprecationWarning
)
if filter_by is not None:
raise ValueError("both 'filter_by' and 'function' were provided; please only use 'filter_by', as 'function' is deprecated")

filter_func = filter_by or function

if filter_func:
return next(iter(filter(filter_func, self.__iterable)), default)
else:
return next(iter(self.__iterable), default)

Expand Down Expand Up @@ -381,24 +393,24 @@ def get(self, index):

return list(self.__iterable)[index]

def last(self, function=None, default=None):
""" Equivalent to calling **next( iter( reversed( list( filter(** *function, iterable* **) ) ) )** *, default* **)**
def last(self, filter_by=None, default=None):
""" Equivalent to calling **next( iter( reversed( list( filter(** *filter_by, iterable* **) ) ) )** *, default* **)**
:param function: keyword-only; function used to filter unwanted values
:param default: keyword-only value to return if *self* is empty after filtered by *function*
:return: last value of *self* filtered by *function*
:param filter_by: keyword-only; function used to filter unwanted values
:param default: keyword-only value to return if *self* is empty after filtered by *filter_by*
:return: last value of *self* filtered by *filter_by*
>>> values = Iterable([1, 2, 5, 9])
>>> values.last()
9
>>> values.last(function=lambda x: x < 5)
>>> values.last(filter_by=lambda x: x < 5)
2
>>> values.last(function=lambda x: x < 1) # Returns None
>>> values.last(function=lambda x: x < 1, default=0)
>>> values.last(filter_by=lambda x: x < 1) # Returns None
>>> values.last(filter_by=lambda x: x < 1, default=0)
0
"""
if function:
reversed_iterable = reversed(list(filter(function, self.__iterable)))
if filter_by:
reversed_iterable = reversed(list(filter(filter_by, self.__iterable)))
else:
reversed_iterable = reversed(list(self.__iterable))

Expand Down
50 changes: 43 additions & 7 deletions tests/test_iterable.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ def test_single_withFunc_returnsMatchingElement(self):

self.assertEqual(
expected_value,
Iterable(test_input).single(function=func)
Iterable(test_input).single(filter_by=func)
)

def test_single_emptyIterableWithNoDefault_returnsNone(self):
Expand Down Expand Up @@ -617,7 +617,7 @@ def test_single_noneMatchingFuncWithNoDefault_returnsNone(self):
with self.subTest(test_input=test_input):
self.assertEqual(
None,
Iterable(test_input).single(function=func)
Iterable(test_input).single(filter_by=func)
)

def test_single_noneMatchingFuncWithDefault_returnsDefault(self):
Expand All @@ -629,7 +629,7 @@ def test_single_noneMatchingFuncWithDefault_returnsDefault(self):
with self.subTest(test_input=test_input):
self.assertEqual(
default,
Iterable(test_input).single(function=func, default=default)
Iterable(test_input).single(filter_by=func, default=default)
)

def test_single_iterableHasMoreThanOneValue_raisesValueError(self):
Expand All @@ -646,7 +646,7 @@ def test_single_multipleMatchingFunc_RaisesValueError(self):
func = lambda x: x in matching_values

with self.assertRaises(ValueError):
Iterable(test_input).single(function=func)
Iterable(test_input).single(filter_by=func)

def test_concat_leftAndRightHasSameContents_returnsLeftConcatRight(self):
for test_input in self.__test_inputs:
Expand Down Expand Up @@ -728,6 +728,12 @@ def test_first_withFunc_returnsFirstMatchingElement(self):
expected_first = list(test_input)[-1]
func = lambda x: x == expected_first

self.assertEqual(
expected_first,
Iterable(test_input).first(filter_by=func)
)

# TODO: Temporary until deprecated parameter is removed
self.assertEqual(
expected_first,
Iterable(test_input).first(function=func)
Expand Down Expand Up @@ -756,6 +762,12 @@ def test_first_noneMatchingFuncWithNoDefault_returnsNone(self):

for test_input in self.__test_inputs:
with self.subTest(test_input=test_input):
self.assertEqual(
None,
Iterable(test_input).first(filter_by=func)
)

# TODO: Temporary until deprecated parameter is removed
self.assertEqual(
None,
Iterable(test_input).first(function=func)
Expand All @@ -768,11 +780,35 @@ def test_first_noneMatchingFuncWithDefault_returnsDefault(self):

for test_input in self.__test_inputs:
with self.subTest(test_input=test_input):
self.assertEqual(
default,
Iterable(test_input).first(filter_by=func, default=default)
)

# TODO: Temporary until deprecated parameter is removed
self.assertEqual(
default,
Iterable(test_input).first(function=func, default=default)
)

def test_first_usingFunctionParameter_raisesWarning(self):
func = lambda x: x == '32bf4b67-42f6-4a86-8229-aa10da364ff7'
default = "default"

for test_input in self.__test_inputs:
with self.subTest(test_input=test_input):
with self.assertWarns(DeprecationWarning):
Iterable(test_input).first(default=default, function=func)

def test_first_filterByAndFunctionProvided_raisesValueError(self):
func = lambda x: x == '32bf4b67-42f6-4a86-8229-aa10da364ff7'
default = "default"

for test_input in self.__test_inputs:
with self.subTest(test_input=test_input):
with self.assertRaises(ValueError):
Iterable(test_input).first(filter_by=func, default=default, function=func)

def test_get_indexInsideBounds_returnsValue(self):
for test_input in self.__test_inputs:
with self.subTest(test_input=test_input):
Expand Down Expand Up @@ -831,7 +867,7 @@ def test_last_withFunc_returnsLastMatchingElement(self):

self.assertEqual(
expected_last,
Iterable(test_input).last(function=func)
Iterable(test_input).last(filter_by=func)
)

def test_last_emptyIterableWithNoDefault_returnsNone(self):
Expand Down Expand Up @@ -859,7 +895,7 @@ def test_last_noneMatchingFuncWithNoDefault_returnsNone(self):
with self.subTest(test_input=test_input):
self.assertEqual(
None,
Iterable(test_input).last(function=func)
Iterable(test_input).last(filter_by=func)
)

def test_last_noneMatchingFuncWithDefault_returnsDefault(self):
Expand All @@ -871,7 +907,7 @@ def test_last_noneMatchingFuncWithDefault_returnsDefault(self):
with self.subTest(test_input=test_input):
self.assertEqual(
default,
Iterable(test_input).last(function=func, default=default)
Iterable(test_input).last(filter_by=func, default=default)
)

def test_skip_countIsZero_returnsIterable(self):
Expand Down

0 comments on commit 454f808

Please sign in to comment.