From c55f605289d43828cedda1d8d1356b78e3567c08 Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sat, 18 Jan 2025 21:50:19 +0000 Subject: [PATCH 01/15] Add parametrize to test_functional.py --- functional/test/test_functional.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/functional/test/test_functional.py b/functional/test/test_functional.py index eb28f72..f35e7d2 100644 --- a/functional/test/test_functional.py +++ b/functional/test/test_functional.py @@ -8,6 +8,8 @@ from functional.transformations import name from functional import seq, pseq +from parametrize import parametrize # type: ignore + Data = namedtuple("Data", "x y") From e7a498f40fd507c9ea355cd2aeae50b173e3970c Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sun, 19 Jan 2025 18:03:13 +0000 Subject: [PATCH 02/15] Add doc to function --- run-tests.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/run-tests.sh b/run-tests.sh index c9dc520..8aa775b 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -1,4 +1,6 @@ +# campare_versions(v1, v2) +# Compares two 3-part sematic versions, returning -1 if v1 is less than v2, 1 if v1 is greater than v2 or 0 if v1 and v2 are equal. compare_versions() { local v1=(${1//./ }) local v2=(${2//./ }) From 29258ce80f8e9e49e9b085d909a92da08896242c Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sun, 19 Jan 2025 17:50:39 +0000 Subject: [PATCH 03/15] Add function to capitalize words --- run-tests.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/run-tests.sh b/run-tests.sh index 8aa775b..1e08746 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -22,6 +22,13 @@ compare_versions() { python_version=$(python --version | grep -Eo \[0-9\]\.\[0-9\]+\.\[0-9\]+) echo "Python version: $python_version" +# capitalise(word) +# Capitalizes a word. +capitalize() { + local word=$1 + echo "$(tr '[:lower:]' '[:upper:]' <<< ${word:0:1})${word:1}" +} + pipx_version=$(pipx --version) if [[ -z "$pipx_version" ]]; then From ad49570ac8a23756411e570e99f593e67cc64f1c Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sun, 19 Jan 2025 17:52:06 +0000 Subject: [PATCH 04/15] Add function to get sematic version of a package installed in Pipx --- run-tests.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index 1e08746..7077a90 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -20,8 +20,15 @@ compare_versions() { echo 0 } -python_version=$(python --version | grep -Eo \[0-9\]\.\[0-9\]+\.\[0-9\]+) -echo "Python version: $python_version" +# get_version_in_pipx(package_name) +# Gets the standard semantic version of a package installed in Pipx if installed. +get_version_in_pipx() { + local package_name=$1 + local version + version=$(pipx list | grep -oP "$package_name"\\s+\\K\[0-9\]+\.\[0-9\]+\.\[0-9\]+) + echo "$version" +} + # capitalise(word) # Capitalizes a word. capitalize() { From 4f8fa79fbdeb38a13444ba1810fca257335a620a Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sun, 19 Jan 2025 17:54:56 +0000 Subject: [PATCH 05/15] Add function to print software and version --- run-tests.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/run-tests.sh b/run-tests.sh index 7077a90..e31a5d6 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -36,6 +36,17 @@ capitalize() { echo "$(tr '[:lower:]' '[:upper:]' <<< ${word:0:1})${word:1}" } +# print_version(name, version, capitalize, width) +# Prints the version of the software with option to capitalize name and change left-aligned padding. +print_version() { + local name=$1 + local version=$2 + local capitalize=${3:-true} + local width=${4:-19} + name=$([[ $capitalize == 'true' ]] && capitalize "$name" || echo "$name") + printf "%-${width}s %s\n" "$name version:" "$version" +} + pipx_version=$(pipx --version) if [[ -z "$pipx_version" ]]; then From 3245568a93c7f78d3f4288e9f905118ad726ec20 Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sun, 19 Jan 2025 17:56:02 +0000 Subject: [PATCH 06/15] Add function to install a package --- run-tests.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/run-tests.sh b/run-tests.sh index e31a5d6..c323e65 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -47,6 +47,22 @@ print_version() { printf "%-${width}s %s\n" "$name version:" "$version" } +# install_package(package_name) +# Installs specified package with Pipx or displays the its version if it's already installed. +install_package() { + local package_name=$1 + local capitalize=${2:-true} + + local version + version=$(get_version_in_pipx "$package_name") + if [[ -n $version ]]; then + print_version "$package_name" "$version" "$capitalize" + else + pipx install "$package_name" + pipx ensurepath + fi +} + pipx_version=$(pipx --version) if [[ -z "$pipx_version" ]]; then From 67aec3426567c1491f7f4e23088307d02b41ef84 Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sun, 19 Jan 2025 17:58:42 +0000 Subject: [PATCH 07/15] Improve Pipx installation message --- run-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-tests.sh b/run-tests.sh index c323e65..5942bc3 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -66,7 +66,7 @@ install_package() { pipx_version=$(pipx --version) if [[ -z "$pipx_version" ]]; then - echo "Pipx is not installed" + echo "Please install Pipx before running this script." exit 1 else echo "Pipx version: $pipx_version" From fcae4ce267f86cee1c3216960e1eb8e44dd51ce0 Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sun, 19 Jan 2025 17:59:49 +0000 Subject: [PATCH 08/15] Print correctly aligned Pipx version --- run-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-tests.sh b/run-tests.sh index 5942bc3..f720d63 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -69,7 +69,7 @@ if [[ -z "$pipx_version" ]]; then echo "Please install Pipx before running this script." exit 1 else - echo "Pipx version: $pipx_version" + print_version "Pipx" "$pipx_version" fi poetry_version=$(pipx list | grep -oP poetry\\s+\\K\[0-9\]\.\[0-9\]+\.\[0-9\]+) From 868fb348c4a5fdf0e69db12e72d832e2a4048a9c Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sun, 19 Jan 2025 18:00:52 +0000 Subject: [PATCH 09/15] Use new function to install packages if missing or print version --- run-tests.sh | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index f720d63..1dc9230 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -72,12 +72,9 @@ else print_version "Pipx" "$pipx_version" fi -poetry_version=$(pipx list | grep -oP poetry\\s+\\K\[0-9\]\.\[0-9\]+\.\[0-9\]+) -if [[ -n $poetry_version ]]; then - echo "Poetry version: $poetry_version" -else - pipx install poetry -fi +install_package "poetry" + +install_package "pre-commit" false echo From 42eb80b98c066e172d86cd8482fb97c65ea674e2 Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sun, 19 Jan 2025 18:02:45 +0000 Subject: [PATCH 10/15] Put remaining standalone commands in a main function --- run-tests.sh | 58 +++++++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index 1dc9230..83e3796 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -63,42 +63,48 @@ install_package() { fi } +main() { + python_version=$(python --version | grep -Eo \[0-9\]\.\[0-9\]+\.\[0-9\]+) + print_version "Python" "$python_version" + + pipx_version=$(pipx --version) + if [[ -z "$pipx_version" ]]; then + echo "Please install Pipx before running this script." + exit 1 + else + print_version "Pipx" "$pipx_version" + fi -pipx_version=$(pipx --version) -if [[ -z "$pipx_version" ]]; then - echo "Please install Pipx before running this script." - exit 1 -else - print_version "Pipx" "$pipx_version" -fi + install_package "poetry" -install_package "poetry" + install_package "pre-commit" false -install_package "pre-commit" false + echo -echo + if ! poetry install; then + poetry lock + poetry install + fi -if ! poetry install; then - poetry lock - poetry install -fi + echo -echo + if [[ $(compare_versions "$python_version" "3.12.0") -lt 0 ]]; then + poetry run pylint functional + else + poetry run ruff check functional + fi -if [[ $(compare_versions "$python_version" "3.12.0") -lt 0 ]]; then - poetry run pylint functional -else - poetry run ruff check functional -fi + echo -echo + poetry run black --diff --color --check functional -poetry run black --diff --color --check functional + echo -echo + poetry run mypy functional -poetry run mypy functional + echo -echo + poetry run pytest +} -poetry run pytest \ No newline at end of file +main "$@" \ No newline at end of file From 981dd14709bbbb3b79d0792c4693fd0ef44cb6ff Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sun, 19 Jan 2025 18:03:58 +0000 Subject: [PATCH 11/15] Upgrade pre-commit-hooks to latest version --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e0818f4..373d7a4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-yaml - id: check-added-large-files From 3ea713830dac3fcfed45f2dc4349fa2f0356c20a Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sun, 19 Jan 2025 18:05:24 +0000 Subject: [PATCH 12/15] Upgrade ruff-pre-commit to the highest version before failing --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 373d7a4..c6d7c73 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,6 +23,6 @@ repos: pass_filenames: false - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.2 + rev: v0.6.0 hooks: - id: ruff From 4e4c4a16dbd5ddef4520d79512b6e655040ea415 Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sun, 5 Jan 2025 21:34:07 +0000 Subject: [PATCH 13/15] Add plus/append functions that concatenate to sequence --- functional/pipeline.py | 30 ++++++++++++++++++++++++++++++ functional/test/test_functional.py | 16 ++++++++++++++++ functional/test/test_type.py | 4 ++++ functional/transformations.py | 25 ++++++++++++++++++++++++- 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/functional/pipeline.py b/functional/pipeline.py index a6ef89f..b590493 100644 --- a/functional/pipeline.py +++ b/functional/pipeline.py @@ -427,6 +427,36 @@ def tails(self) -> Sequence[Self]: """ return self._transform(transformations.tails_t(_wrap)) + def plus(self, other: Sequence[Any] | Iterable[Any] | Any) -> Sequence[Any]: + """ + Concatenates sequence with other. + + >>> seq([1, 2, 3]).plus(4) + [1, 2, 3, 4] + + >>> seq([1, 2, 3]).plus([4, 5, 6]) + [1, 2, 3, 4, 5, 6] + + :param other: single element or sequence to concatenate + :return: concatenated sequence with other + """ + return self._transform(transformations.plus_t(other)) + + def append(self, other: Sequence[Any] | Iterable[Any] | Any) -> Sequence[Any]: + """ + Concatenates sequence with other. + + >>> seq([1, 2, 3]).plus(4) + [1, 2, 3, 4] + + >>> seq([1, 2, 3]).plus([4, 5, 6]) + [1, 2, 3, 4, 5, 6] + + :param other: single element or sequence to concatenate + :return: concatenated sequence with other + """ + return self._transform(transformations.plus_t(other, "append")) + @overload def cartesian(self, /) -> Sequence[tuple[_T_co]]: ... diff --git a/functional/test/test_functional.py b/functional/test/test_functional.py index f35e7d2..5296ba8 100644 --- a/functional/test/test_functional.py +++ b/functional/test/test_functional.py @@ -264,6 +264,22 @@ def test_tails(self): self.assertIteratorEqual(l.tails(), expect) self.assertIteratorEqual(l.tails().map(lambda s: s.sum()), [6, 5, 3, 0]) + @parametrize( + "sequence, other, expected", + [ + ([1, 2, 3], 4, [1, 2, 3, 4]), + ([1, 2], [3, 4], [1, 2, 3, 4]), + ([1, 2], seq(3, 4), [1, 2, 3, 4]), + ([1, 2], [[3, 4]], [1, 2, [3, 4]]), + ([1, 2], [], [1, 2]), + ([], [], []), + ], + ) + def test_plus(self, sequence, other, expected): + result = self.seq(sequence).plus(other) + self.assertIteratorEqual(expected, result) + self.assert_type(result) + def test_drop(self): s = self.seq([1, 2, 3, 4, 5, 6]) expect = [5, 6] diff --git a/functional/test/test_type.py b/functional/test/test_type.py index c9d0644..d79ffae 100644 --- a/functional/test/test_type.py +++ b/functional/test/test_type.py @@ -44,6 +44,10 @@ def type_checking() -> None: t_tails: Sequence[Sequence[int]] = seq([1, 2, 3]).tails() + t_plus: Sequence[int] = seq([1, 2, 3]).plus(seq([4, 5, 6])) + + t_append: Sequence[int] = seq([1, 2, 3]).append(seq([4, 5, 6])) + t_cartesian: Sequence[tuple[int, int]] = seq.range(2).cartesian(range(2)) t_drop: Sequence[int] = seq([1, 2, 3, 4, 5]).drop(2) diff --git a/functional/transformations.py b/functional/transformations.py index 22ac477..39f9fbd 100644 --- a/functional/transformations.py +++ b/functional/transformations.py @@ -11,7 +11,7 @@ ) import collections import types -from collections.abc import Callable +from collections.abc import Callable, Iterable from functional.execution import ExecutionStrategies @@ -376,6 +376,29 @@ def tails_t(wrap): ) +def plus_t(other, function_name="plus"): + """ + Transformation for Sequence.plus + :param other: single element or sequence to concatenate + :param function_name: name of pipeline function + :return: transformation + """ + + def plus(sequence): + class_name = f"{other.__class__.__module__}.{other.__class__.__name__}" + if class_name == "functional.pipeline.Sequence": + return sequence + other.to_list() + if isinstance(other, Iterable): + return sequence + other + return sequence + [other] + + return Transformation( + f"{function_name}({other})", + plus, + None, + ) + + def union_t(other): """ Transformation for Sequence.union From c98c0b16997baa445410802a1a039f84de0fdddd Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Sun, 5 Jan 2025 22:44:46 +0000 Subject: [PATCH 14/15] Add plus/append functions to Transformations and Actions APIs table --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f6c0e32..44d6757 100644 --- a/README.md +++ b/README.md @@ -366,6 +366,8 @@ complete documentation reference | `tail()` | Returns sequence without the first element | transformation | | `inits()` | Returns consecutive inits of sequence | transformation | | `tails()` | Returns consecutive tails of sequence | transformation | +| `plus(other)` | Concatenates this sequence with `other` | transformation | +| `append(other)` | Synonym for `plus()` | transformation | | `zip(other)` | Zips the sequence with `other` | transformation | | `zip_with_index(start=0)` | Zips the sequence with the index starting at `start` on the right side | transformation | | `enumerate(start=0)` | Zips the sequence with the index starting at `start` on the left side | transformation | From dce0c118c457e89d9a17853ece0abd963de1471b Mon Sep 17 00:00:00 2001 From: Samer Hamood Date: Mon, 20 Jan 2025 21:11:51 +0000 Subject: [PATCH 15/15] Add 'plus' function to CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3fb0d4..311a5a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### New Features +- Implemented the `plus` function that concatenates iterables and single elements as well as other sequences - Added `first_or_none`, a function to match `head_or_none` - Added run_test.sh script - Added [parametrize](https://pypi.org/project/parametrize/) for parameterized unit tests