From 4aa20e1b673e6382424ff8a6cc458edb5fc7231c Mon Sep 17 00:00:00 2001 From: josex2r Date: Thu, 3 Feb 2022 11:44:36 +0100 Subject: [PATCH 1/6] feat: array class --- python_js/__init__.py | 2 ++ python_js/array.py | 9 ++++++ python_js/array/__init__.py | 7 ----- python_js/{array => array_methods}/map.py | 8 ++++-- tests/array/test_array.py | 33 ++++++++++++++++++++++ tests/array/test_map.py | 34 ++++++++++++++++++----- 6 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 python_js/array.py delete mode 100644 python_js/array/__init__.py rename python_js/{array => array_methods}/map.py (86%) create mode 100644 tests/array/test_array.py diff --git a/python_js/__init__.py b/python_js/__init__.py index a88d0e8..954255d 100644 --- a/python_js/__init__.py +++ b/python_js/__init__.py @@ -4,4 +4,6 @@ This is not production safe and it's made for learning purposes. """ +from .array import Array + __author__ = 'Jose Luis Represa' diff --git a/python_js/array.py b/python_js/array.py new file mode 100644 index 0000000..33b6000 --- /dev/null +++ b/python_js/array.py @@ -0,0 +1,9 @@ +# from .array.map import map, CallbackType, ReturnValue +from __future__ import annotations + +from .array_methods.map import map, CallbackType, K + + +class Array(list): + def map(self, func: CallbackType) -> Array[K]: + return Array(map(self, func)) diff --git a/python_js/array/__init__.py b/python_js/array/__init__.py deleted file mode 100644 index 8a43a4d..0000000 --- a/python_js/array/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Array utils. - -Export all things -""" - -from .map import map diff --git a/python_js/array/map.py b/python_js/array_methods/map.py similarity index 86% rename from python_js/array/map.py rename to python_js/array_methods/map.py index c1e9140..84a1b8f 100644 --- a/python_js/array/map.py +++ b/python_js/array_methods/map.py @@ -1,6 +1,5 @@ from __future__ import annotations - -from typing import Iterable, List, Protocol, TypeVar # Protocol +from typing import Iterable, Protocol, TypeVar, List # Protocol # P = ParamSpec('P') T = TypeVar('T', contravariant=True) @@ -30,7 +29,10 @@ def __call__( pass -def map(iterable: Iterable[T], func: Callback[T, K]) -> List[K]: +CallbackType = Callback[T, K] + + +def map(iterable: Iterable[T], func: CallbackType) -> List[K]: """ Return a new array with the results of calling func on each element. diff --git a/tests/array/test_array.py b/tests/array/test_array.py new file mode 100644 index 0000000..3ab48cd --- /dev/null +++ b/tests/array/test_array.py @@ -0,0 +1,33 @@ +from __future__ import annotations +from typing import List + +from python_js.array import Array + +seasons: List[str] = ['Winter', 'Summer', 'Fall', 'Spring'] +numbers: List[int] = [1, 2] +seasond_and_numbers: List[int | str] = [*seasons, *numbers] + + +def test_constructor(): + # Then + assert Array(seasond_and_numbers) == seasond_and_numbers + + +def test_map(): + array = Array(seasond_and_numbers) + result = array.map(lambda x, i: f"{x} ({i})") + + # Then + assert result == [ + 'Winter (0)', 'Summer (1)', 'Fall (2)', 'Spring (3)', '1 (4)', '2 (5)' + ] + + +def test_map_chainable(): + array = Array(seasond_and_numbers) + result = array.map(lambda x, i: f"{x} ({i})").map(lambda x, i: f"({i}) {x}") + + # Then + assert result == [ + '(0) Winter (0)', '(1) Summer (1)', '(2) Fall (2)', '(3) Spring (3)', '(4) 1 (4)', '(5) 2 (5)' + ] diff --git a/tests/array/test_map.py b/tests/array/test_map.py index d5288f7..7905d01 100644 --- a/tests/array/test_map.py +++ b/tests/array/test_map.py @@ -1,20 +1,40 @@ from __future__ import annotations from typing import Callable, List -from python_js.array import map +from python_js.array_methods.map import map +from python_js.array import Array seasons: List[str] = ['Winter', 'Summer', 'Fall', 'Spring'] -numbers: List[int] = [1, 2, 3, 4] -seasond_and_numbers: List[int | str] = [*seasons, *numbers] +numbers: List[int] = [1, 2] +seasons_and_numbers: List[int | str] = [*seasons, *numbers] -def test_map(): +def test_returned_value_instance(): + formatted_seasons = map(seasons, lambda x, i: x) + + # Then + assert isinstance(formatted_seasons, List) + + +def test_input_argument_iterable(): + format_str: Callable[[str, int], + str] = lambda value, index: f"{value} ({index})" + + formatted_seasons_and_numbers = map(seasons_and_numbers, format_str) + + # Then + assert formatted_seasons_and_numbers == [ + 'Winter (0)', 'Summer (1)', 'Fall (2)', 'Spring (3)', '1 (4)', '2 (5)' + ] + + +def test_input_argument_array(): format_str: Callable[[str, int], str] = lambda value, index: f"{value} ({index})" - formatted_seasons = map(seasons, format_str) + formatted_seasons_and_numbers = map(Array(seasons_and_numbers), format_str) # Then - assert formatted_seasons == [ - 'Winter (0)', 'Summer (1)', 'Fall (2)', 'Spring (3)' + assert formatted_seasons_and_numbers == [ + 'Winter (0)', 'Summer (1)', 'Fall (2)', 'Spring (3)', '1 (4)', '2 (5)' ] From 67097e06b6d1943b87f43f05abcf0f67a1dca922 Mon Sep 17 00:00:00 2001 From: josex2r Date: Thu, 3 Feb 2022 19:35:40 +0100 Subject: [PATCH 2/6] feat: filter array --- python_js/array.py | 15 +++++++++--- python_js/array_methods/__init__.py | 2 ++ python_js/array_methods/filter.py | 23 +++++++++++++++++ python_js/array_methods/map.py | 33 +++---------------------- tests/array/test_array.py | 38 +++++++++++++++++++++++------ tests/array/test_filter.py | 30 +++++++++++++++++++++++ tests/array/test_map.py | 16 ++++++------ 7 files changed, 109 insertions(+), 48 deletions(-) create mode 100644 python_js/array_methods/__init__.py create mode 100644 python_js/array_methods/filter.py create mode 100644 tests/array/test_filter.py diff --git a/python_js/array.py b/python_js/array.py index 33b6000..c54fcb8 100644 --- a/python_js/array.py +++ b/python_js/array.py @@ -1,9 +1,18 @@ # from .array.map import map, CallbackType, ReturnValue from __future__ import annotations -from .array_methods.map import map, CallbackType, K +from typing import List, TypeVar, Callable, Any +from .array_methods.map import map +from .array_methods.filter import filter -class Array(list): - def map(self, func: CallbackType) -> Array[K]: +T = TypeVar("T") +K = TypeVar("K") + + +class Array(List[T]): + def filter(self, func: Callable[[T, int], bool]) -> Array[Any]: + return Array(filter(self, func)) + + def map(self, func: Callable[[T, int], K]) -> Array[K]: return Array(map(self, func)) diff --git a/python_js/array_methods/__init__.py b/python_js/array_methods/__init__.py new file mode 100644 index 0000000..6878d49 --- /dev/null +++ b/python_js/array_methods/__init__.py @@ -0,0 +1,2 @@ +from .filter import filter +from .map import map diff --git a/python_js/array_methods/filter.py b/python_js/array_methods/filter.py new file mode 100644 index 0000000..85a79c5 --- /dev/null +++ b/python_js/array_methods/filter.py @@ -0,0 +1,23 @@ +from __future__ import annotations +from typing import Iterable, TypeVar, List, Callable, Any + +T = TypeVar('T') + + +def filter(iterable: Iterable[T], func: Callable[[T, int], bool]) -> List[Any]: + """ + Return a new array with the the elements which passes the callback. + + Args: + iterable: The iterable to map. + func: The callback which filters the values. + + Return: + A filtered List. + + Examples + -------- + filter([1, 2, 3], lambda x: x > 2) # [3] + + """ + return [value for index, value in enumerate(iterable) if func(value, index)] diff --git a/python_js/array_methods/map.py b/python_js/array_methods/map.py index 84a1b8f..b0f767f 100644 --- a/python_js/array_methods/map.py +++ b/python_js/array_methods/map.py @@ -1,38 +1,11 @@ from __future__ import annotations -from typing import Iterable, Protocol, TypeVar, List # Protocol +from typing import Iterable, TypeVar, List, Callable -# P = ParamSpec('P') -T = TypeVar('T', contravariant=True) +T = TypeVar('T') K = TypeVar('K') -T_contra = TypeVar('T_contra', contravariant=True) -K_co = TypeVar('K_co', covariant=True) - -class Callback(Protocol[T_contra, K_co]): - """Definition of a callback function protocol.""" - - def __call__( - self, - value: T_contra, - index: int, - ) -> K_co: - """ - Definition of a callback function. - - Args: - ---- - value: An element of the iterable. - index: The index of the element. - - """ - pass - - -CallbackType = Callback[T, K] - - -def map(iterable: Iterable[T], func: CallbackType) -> List[K]: +def map(iterable: Iterable[T], func: Callable[[T, int], K]) -> List[K]: """ Return a new array with the results of calling func on each element. diff --git a/tests/array/test_array.py b/tests/array/test_array.py index 3ab48cd..4442b65 100644 --- a/tests/array/test_array.py +++ b/tests/array/test_array.py @@ -1,20 +1,21 @@ from __future__ import annotations + from typing import List from python_js.array import Array seasons: List[str] = ['Winter', 'Summer', 'Fall', 'Spring'] numbers: List[int] = [1, 2] -seasond_and_numbers: List[int | str] = [*seasons, *numbers] +seasons_and_numbers: List[int | str] = [*seasons, *numbers] -def test_constructor(): +def test_constructor() -> None: # Then - assert Array(seasond_and_numbers) == seasond_and_numbers + assert Array(seasons_and_numbers) == seasons_and_numbers -def test_map(): - array = Array(seasond_and_numbers) +def test_map() -> None: + array = Array(seasons_and_numbers) result = array.map(lambda x, i: f"{x} ({i})") # Then @@ -23,11 +24,32 @@ def test_map(): ] -def test_map_chainable(): - array = Array(seasond_and_numbers) - result = array.map(lambda x, i: f"{x} ({i})").map(lambda x, i: f"({i}) {x}") +def test_map_chainable() -> None: + array = Array(seasons_and_numbers) + result = array.map(lambda x, i: f"{x} ({i})").map( + lambda x, i: f"({i}) {x}") # Then assert result == [ '(0) Winter (0)', '(1) Summer (1)', '(2) Fall (2)', '(3) Spring (3)', '(4) 1 (4)', '(5) 2 (5)' ] + + +def test_filter() -> None: + array = Array(seasons_and_numbers) + result = array.filter(lambda x, i: isinstance(x, str)) + + # Then + assert result == ["Winter", "Summer", "Fall", "Spring"] + + +def test_filter_chainable() -> None: + array = Array(seasons_and_numbers) + result: List[str] = array.filter( + lambda x, i: isinstance(x, str) + ).filter( + lambda x, i: x.startswith('W') + ) + + # Then + assert result == ["Winter"] diff --git a/tests/array/test_filter.py b/tests/array/test_filter.py new file mode 100644 index 0000000..42c0084 --- /dev/null +++ b/tests/array/test_filter.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from typing import Callable, List + +from python_js.array import Array +from python_js.array_methods.filter import filter + +seasons: List[str] = ['Winter', 'Summer', 'Fall', 'Spring'] +numbers: List[int] = [1, 2] +seasons_and_numbers: List[int | str] = [*seasons, *numbers] + + +def test_returned_value_instance() -> None: + formatted_seasons: List[str] = filter(seasons, lambda x, i: bool(x)) + + # Then + assert isinstance(formatted_seasons, List) + + +def test_input_argument_iterable() -> None: + format_str: Callable[[str | int, int], + bool] = lambda value, index: isinstance(value, str) + + formatted_seasons_and_numbers: List[str] = filter( + seasons_and_numbers, format_str) + + # Then + assert formatted_seasons_and_numbers == [ + 'Winter', 'Summer', 'Fall', 'Spring' + ] diff --git a/tests/array/test_map.py b/tests/array/test_map.py index 7905d01..13ffc12 100644 --- a/tests/array/test_map.py +++ b/tests/array/test_map.py @@ -1,26 +1,28 @@ from __future__ import annotations + from typing import Callable, List -from python_js.array_methods.map import map from python_js.array import Array +from python_js.array_methods.map import map seasons: List[str] = ['Winter', 'Summer', 'Fall', 'Spring'] numbers: List[int] = [1, 2] seasons_and_numbers: List[int | str] = [*seasons, *numbers] -def test_returned_value_instance(): - formatted_seasons = map(seasons, lambda x, i: x) +def test_returned_value_instance() -> None: + formatted_seasons: List[str] = map(seasons, lambda x, i: x) # Then assert isinstance(formatted_seasons, List) -def test_input_argument_iterable(): - format_str: Callable[[str, int], +def test_input_argument_iterable() -> None: + format_str: Callable[[str | int, int], str] = lambda value, index: f"{value} ({index})" - formatted_seasons_and_numbers = map(seasons_and_numbers, format_str) + formatted_seasons_and_numbers: List[str] = map( + seasons_and_numbers, format_str) # Then assert formatted_seasons_and_numbers == [ @@ -28,7 +30,7 @@ def test_input_argument_iterable(): ] -def test_input_argument_array(): +def test_input_argument_array() -> None: format_str: Callable[[str, int], str] = lambda value, index: f"{value} ({index})" From 2b7a1fa54f432c412716692b5056cba2e09de861 Mon Sep 17 00:00:00 2001 From: josex2r Date: Thu, 3 Feb 2022 19:59:51 +0100 Subject: [PATCH 3/6] feat: reduce array --- python_js/array.py | 6 ++-- python_js/array_methods/__init__.py | 1 + python_js/array_methods/reduce.py | 36 +++++++++++++++++++++ tests/array/test_map.py | 12 ------- tests/array/test_reduce.py | 49 +++++++++++++++++++++++++++++ tests/{array => }/test_array.py | 38 +++++++++++++--------- 6 files changed, 113 insertions(+), 29 deletions(-) create mode 100644 python_js/array_methods/reduce.py create mode 100644 tests/array/test_reduce.py rename tests/{array => }/test_array.py (86%) diff --git a/python_js/array.py b/python_js/array.py index c54fcb8..c8b0609 100644 --- a/python_js/array.py +++ b/python_js/array.py @@ -3,8 +3,7 @@ from typing import List, TypeVar, Callable, Any -from .array_methods.map import map -from .array_methods.filter import filter +from .array_methods import filter, map, reduce T = TypeVar("T") K = TypeVar("K") @@ -16,3 +15,6 @@ def filter(self, func: Callable[[T, int], bool]) -> Array[Any]: def map(self, func: Callable[[T, int], K]) -> Array[K]: return Array(map(self, func)) + + def reduce(self, func: Callable[[Any, T, int], Any], initialValue: K | None = None) -> Any: + return reduce(self, func, initialValue=initialValue) diff --git a/python_js/array_methods/__init__.py b/python_js/array_methods/__init__.py index 6878d49..ca3719a 100644 --- a/python_js/array_methods/__init__.py +++ b/python_js/array_methods/__init__.py @@ -1,2 +1,3 @@ from .filter import filter from .map import map +from .reduce import reduce diff --git a/python_js/array_methods/reduce.py b/python_js/array_methods/reduce.py new file mode 100644 index 0000000..0844385 --- /dev/null +++ b/python_js/array_methods/reduce.py @@ -0,0 +1,36 @@ +from __future__ import annotations +from typing import Iterable, TypeVar, List, Callable, Union, Any + +T = TypeVar('T') +K = TypeVar('K') + + +def reduce(iterable: Iterable[T], func: Callable[[Any, T, int], Any], initialValue: K | None = None) -> Any: + """ + Executes a user-supplied “reducer” callback function on each element of + the array, in order, passing in the return value from the calculation on + the preceding element. The final result of running the reducer across all + elements of the array is a single value. + + Args: + iterable: The iterable to map. + func: The callback which transforms all the values. + + Return: + A new element. + + Examples + -------- + reduce( + ["a", "b", "c"], + lambda acc, x, i: (acc[x] = i) and acc, + {} + ) # { 'a': 0, 'b': 1, 'c': 2 } + + """ + result = initialValue + + for i, x in enumerate(iterable): + result = func(result, x, i) + + return result diff --git a/tests/array/test_map.py b/tests/array/test_map.py index 13ffc12..31802d5 100644 --- a/tests/array/test_map.py +++ b/tests/array/test_map.py @@ -28,15 +28,3 @@ def test_input_argument_iterable() -> None: assert formatted_seasons_and_numbers == [ 'Winter (0)', 'Summer (1)', 'Fall (2)', 'Spring (3)', '1 (4)', '2 (5)' ] - - -def test_input_argument_array() -> None: - format_str: Callable[[str, int], - str] = lambda value, index: f"{value} ({index})" - - formatted_seasons_and_numbers = map(Array(seasons_and_numbers), format_str) - - # Then - assert formatted_seasons_and_numbers == [ - 'Winter (0)', 'Summer (1)', 'Fall (2)', 'Spring (3)', '1 (4)', '2 (5)' - ] diff --git a/tests/array/test_reduce.py b/tests/array/test_reduce.py new file mode 100644 index 0000000..2009541 --- /dev/null +++ b/tests/array/test_reduce.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from typing import Callable, Dict, List + +from python_js.array import Array +from python_js.array_methods.reduce import reduce + +seasons: List[str] = ['Winter', 'Summer', 'Fall', 'Spring'] +numbers: List[int] = [1, 2] +seasons_and_numbers: List[int | str] = [*seasons, *numbers] + + +def test_returned_value_instance() -> None: + formatted_seasons: str = reduce(seasons, lambda x, y, i: y) + + # Then + assert isinstance(formatted_seasons, str) + + +def test_input_argument_iterable() -> None: + concat_str: Callable[[str, str | int, int], + str] = lambda prev, next, index: f"{prev}, {next}" + + formatted_seasons_and_numbers: str = reduce( + seasons_and_numbers, concat_str) + + # Then + assert formatted_seasons_and_numbers == 'None, Winter, Summer, Fall, Spring, 1, 2' + + +def test_initialValue() -> None: + accumulator: Dict[str, int] = {} + + def reduce_seasons(acc: Dict[str, int], season: str, index: int) -> Dict[str, int]: + acc[season] = index + return acc + + seasons_dict: Dict[str, int] = reduce( + seasons, reduce_seasons, initialValue=accumulator) + + # Then + assert seasons_dict == { + 'Winter': 0, + 'Summer': 1, + 'Fall': 2, + 'Spring': 3, + } + assert seasons_dict == accumulator + assert id(seasons_dict) == id(accumulator) diff --git a/tests/array/test_array.py b/tests/test_array.py similarity index 86% rename from tests/array/test_array.py rename to tests/test_array.py index 4442b65..51ed60c 100644 --- a/tests/array/test_array.py +++ b/tests/test_array.py @@ -14,6 +14,26 @@ def test_constructor() -> None: assert Array(seasons_and_numbers) == seasons_and_numbers +def test_filter() -> None: + array = Array(seasons_and_numbers) + result = array.filter(lambda x, i: isinstance(x, str)) + + # Then + assert result == ["Winter", "Summer", "Fall", "Spring"] + + +def test_filter_chainable() -> None: + array = Array(seasons_and_numbers) + result: List[str] = array.filter( + lambda x, i: isinstance(x, str) + ).filter( + lambda x, i: x.startswith('W') + ) + + # Then + assert result == ["Winter"] + + def test_map() -> None: array = Array(seasons_and_numbers) result = array.map(lambda x, i: f"{x} ({i})") @@ -35,21 +55,9 @@ def test_map_chainable() -> None: ] -def test_filter() -> None: +def test_reduce() -> None: array = Array(seasons_and_numbers) - result = array.filter(lambda x, i: isinstance(x, str)) + result = array.reduce(lambda prev, next, index: f"{prev}, {next}") # Then - assert result == ["Winter", "Summer", "Fall", "Spring"] - - -def test_filter_chainable() -> None: - array = Array(seasons_and_numbers) - result: List[str] = array.filter( - lambda x, i: isinstance(x, str) - ).filter( - lambda x, i: x.startswith('W') - ) - - # Then - assert result == ["Winter"] + assert result == 'None, Winter, Summer, Fall, Spring, 1, 2' From 1d2c200ee8284865690995b7abe75fb8dd9342b7 Mon Sep 17 00:00:00 2001 From: josex2r Date: Fri, 4 Feb 2022 11:40:19 +0100 Subject: [PATCH 4/6] style: lint issues --- python_js/__init__.py | 4 +++- python_js/array_methods/__init__.py | 8 ++++++++ python_js/array_methods/filter.py | 4 +++- python_js/array_methods/reduce.py | 2 +- setup.cfg | 3 +++ tests/array/test_filter.py | 1 - tests/array/test_map.py | 1 - tests/array/test_reduce.py | 1 - tests/test_array.py | 7 ++++++- 9 files changed, 24 insertions(+), 7 deletions(-) diff --git a/python_js/__init__.py b/python_js/__init__.py index 954255d..81b91eb 100644 --- a/python_js/__init__.py +++ b/python_js/__init__.py @@ -6,4 +6,6 @@ from .array import Array -__author__ = 'Jose Luis Represa' +__all__ = [ + "Array", +] diff --git a/python_js/array_methods/__init__.py b/python_js/array_methods/__init__.py index ca3719a..4ae6c4d 100644 --- a/python_js/array_methods/__init__.py +++ b/python_js/array_methods/__init__.py @@ -1,3 +1,11 @@ from .filter import filter from .map import map from .reduce import reduce + +# __all__ = tuple(k for k in locals() if not k.startswith("_")) + +__all__ = [ + "filter", + "map", + "reduce" +] diff --git a/python_js/array_methods/filter.py b/python_js/array_methods/filter.py index 85a79c5..d84810f 100644 --- a/python_js/array_methods/filter.py +++ b/python_js/array_methods/filter.py @@ -20,4 +20,6 @@ def filter(iterable: Iterable[T], func: Callable[[T, int], bool]) -> List[Any]: filter([1, 2, 3], lambda x: x > 2) # [3] """ - return [value for index, value in enumerate(iterable) if func(value, index)] + return [ + value for index, value in enumerate(iterable) if func(value, index) + ] diff --git a/python_js/array_methods/reduce.py b/python_js/array_methods/reduce.py index 0844385..6e5b0c8 100644 --- a/python_js/array_methods/reduce.py +++ b/python_js/array_methods/reduce.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Iterable, TypeVar, List, Callable, Union, Any +from typing import Iterable, TypeVar, Callable, Any T = TypeVar('T') K = TypeVar('K') diff --git a/setup.cfg b/setup.cfg index 738fa4c..a747276 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ [semantic_release] version_variable = setup.py:__version__ + +[flake8] +max-line-length = 120 diff --git a/tests/array/test_filter.py b/tests/array/test_filter.py index 42c0084..37d7681 100644 --- a/tests/array/test_filter.py +++ b/tests/array/test_filter.py @@ -2,7 +2,6 @@ from typing import Callable, List -from python_js.array import Array from python_js.array_methods.filter import filter seasons: List[str] = ['Winter', 'Summer', 'Fall', 'Spring'] diff --git a/tests/array/test_map.py b/tests/array/test_map.py index 31802d5..824500d 100644 --- a/tests/array/test_map.py +++ b/tests/array/test_map.py @@ -2,7 +2,6 @@ from typing import Callable, List -from python_js.array import Array from python_js.array_methods.map import map seasons: List[str] = ['Winter', 'Summer', 'Fall', 'Spring'] diff --git a/tests/array/test_reduce.py b/tests/array/test_reduce.py index 2009541..a245f8f 100644 --- a/tests/array/test_reduce.py +++ b/tests/array/test_reduce.py @@ -2,7 +2,6 @@ from typing import Callable, Dict, List -from python_js.array import Array from python_js.array_methods.reduce import reduce seasons: List[str] = ['Winter', 'Summer', 'Fall', 'Spring'] diff --git a/tests/test_array.py b/tests/test_array.py index 51ed60c..b1bf678 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -51,7 +51,12 @@ def test_map_chainable() -> None: # Then assert result == [ - '(0) Winter (0)', '(1) Summer (1)', '(2) Fall (2)', '(3) Spring (3)', '(4) 1 (4)', '(5) 2 (5)' + '(0) Winter (0)', + '(1) Summer (1)', + '(2) Fall (2)', + '(3) Spring (3)', + '(4) 1 (4)', + '(5) 2 (5)' ] From 335944280ac84412b4363dadfc9ab893002ca954 Mon Sep 17 00:00:00 2001 From: josex2r Date: Fri, 4 Feb 2022 12:02:28 +0100 Subject: [PATCH 5/6] feat: array concat --- python_js/array.py | 5 ++++- python_js/array_methods/__init__.py | 2 ++ python_js/array_methods/concat.py | 30 +++++++++++++++++++++++++++++ tests/array/test_concat.py | 23 ++++++++++++++++++++++ tests/test_array.py | 16 +++++++++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 python_js/array_methods/concat.py create mode 100644 tests/array/test_concat.py diff --git a/python_js/array.py b/python_js/array.py index c8b0609..4d74674 100644 --- a/python_js/array.py +++ b/python_js/array.py @@ -3,13 +3,16 @@ from typing import List, TypeVar, Callable, Any -from .array_methods import filter, map, reduce +from .array_methods import concat, filter, map, reduce T = TypeVar("T") K = TypeVar("K") class Array(List[T]): + def concat(self, *args: List[Any]) -> Array[Any]: + return Array(concat(self, *args)) + def filter(self, func: Callable[[T, int], bool]) -> Array[Any]: return Array(filter(self, func)) diff --git a/python_js/array_methods/__init__.py b/python_js/array_methods/__init__.py index 4ae6c4d..e901649 100644 --- a/python_js/array_methods/__init__.py +++ b/python_js/array_methods/__init__.py @@ -1,3 +1,4 @@ +from .concat import concat from .filter import filter from .map import map from .reduce import reduce @@ -5,6 +6,7 @@ # __all__ = tuple(k for k in locals() if not k.startswith("_")) __all__ = [ + "concat", "filter", "map", "reduce" diff --git a/python_js/array_methods/concat.py b/python_js/array_methods/concat.py new file mode 100644 index 0000000..7665810 --- /dev/null +++ b/python_js/array_methods/concat.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from typing import Any, List + + +def concat(*args: List[Any]) -> List[Any]: + """ + Merge two or more arrays. + This method does not change the existing arrays, but instead returns a new array. + + Args: + *args: The iterables to mergle. + + Return: + A new element. + + Examples + -------- + concat( + ["a", "b", "c"], + [1, 2, 3] + ) # ["a", "b", "c", 1, 2, 3] + + """ + result: List[Any] = [] + + for x in args: + result = [*result, *x] + + return result diff --git a/tests/array/test_concat.py b/tests/array/test_concat.py new file mode 100644 index 0000000..5385923 --- /dev/null +++ b/tests/array/test_concat.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from typing import List + +from python_js.array_methods.concat import concat + +seasons: List[str] = ['Winter', 'Summer', 'Fall', 'Spring'] +numbers: List[int] = [1, 2] +seasons_and_numbers: List[int | str] = [*seasons, *numbers] + + +def test_returned_value_instance() -> None: + formatted_seasons: List[str | int] = concat(seasons, numbers) + + # Then + assert isinstance(formatted_seasons, List) + + +def test_input_argument_iterable() -> None: + result: List[str | int] = concat(seasons, numbers) + + # Then + assert result == seasons_and_numbers diff --git a/tests/test_array.py b/tests/test_array.py index b1bf678..4324dc6 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -14,6 +14,22 @@ def test_constructor() -> None: assert Array(seasons_and_numbers) == seasons_and_numbers +def test_concat() -> None: + array = Array(seasons) + result = array.concat(numbers) + + # Then + assert result == seasons_and_numbers + + +def test_concat_chainable() -> None: + array = Array(seasons) + result: List[str | int] = array.concat(numbers).concat(numbers) + + # Then + assert result == [*seasons_and_numbers, *numbers] + + def test_filter() -> None: array = Array(seasons_and_numbers) result = array.filter(lambda x, i: isinstance(x, str)) From c8f9291d981e9ec6cbab1a61761ea432dee61ca4 Mon Sep 17 00:00:00 2001 From: josex2r Date: Fri, 4 Feb 2022 12:05:14 +0100 Subject: [PATCH 6/6] docs: README --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/README.md b/README.md index e69de29..67827ab 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,53 @@ +# python_js + +`python_js` is a simple Python module which ports some JS functions to Python. + +This is not production safe and it's made for learning purposes. I know this is not Pythonic's stylish but it's just a playground to learn some basics about this language. + +## Features + +- Some array methods +- Type checking +- Linter +- Local publishing using `Pypi` test environment. +- Automatic publish using `Github Actions` + `semantic-release` +- Local & CI testing using `tox` + +## Requirements + +- `python >= 3.10` +- `pipenv` + +## Install + +```bash +pipenv install +``` + +## Running linters + +```bash +pipenv run lint +``` + +## Running tests + +Run all tests using `tox`: + +```bash +tox +``` + +Using the current python version: + +```bash +pipenv run test +``` + +## Publish + +Test `Pypi` from local: + +```bash +pipenv run publish_raw +```